Desde hace un tiempo hasta ahora, el auge de las nubes públicas es cada vez mayor.

Se ha escrito mucho sobre arquitecturas de microservicios en anteriores posts y sobre las tecnologías que trataremos en este post (Kubernetes, Istio) así que el objetivo de este post es, con todos esos conceptos descritos anteriormente, ver qué posibilidades existen en Google Cloud Platform (GCP).

¿Por qué Google?

Parece casi impensable, a día de hoy, diseñar una arquitectura de microservicios sin el concepto de contenedor. Aunque existen otras tecnologías de contenedores, Docker, probablemente, sea la más difundida y soportada.

Y, en paralelo al crecimiento de contenedores ha ido el crecimiento de tecnologías de gestión de contenedores. En este caso, Kubernetes se ha impuesto al resto de tecnologías (Docker Swarm, Mesos).

Existen soluciones empresariales basadas en Kubernetes que apoyan esta tendencia: RedHat con OpenShift o Mesosphere que, aunque de manera muy disimulada, ha puesto el foco en Kubernetes.

Kubernetes fue diseñado por Google y posteriormente donado a la Cloud Native Computing Foundation. En su desarrollo han intervenido, además de Google, empresas como RedHat, Huawei, Microsoft o VMWare, entre otras.

A través de Google Kubernetes Engine (GKE), Google ofrece su servicio gestionado de Kubernetes, haciéndonos la vida más fácil para que nos centremos en la parte más creativa, aportando más valor en mucho menos tiempo.

¿Qué haremos?

  1. Crearemos un cluster de Kubernetes a través del servicio GKE.
  2. Instalaremos Istio como Service Mesh que nos ayudará con ciertas operaciones.
  3. Crearemos dos entornos virtuales a través de namespaces en Kubernetes y habilitaremos Istio para que inyecte los proxies en nuestros pods.
  4. Analizaremos la anatomía de un microservicio y qué componentes tiene que tener para hacer posible su implantación en esta arquitectura.
  5. Desplegaremos un microservicio y lo promocionaremos de entorno.
  6. Haremos que ese microservicio sea visible al mundo exterior.

Creación de un cluster en GKE

gcloud beta container --project projectId-GCP clusters create demo-cluster \
 --zone "us-central1-b" \
 --no-enable-basic-auth \
 --cluster-version "1.12.8-gke.10" \
 --num-nodes "3" \
 --machine-type "n1-standard-2" \
 --disk-type "pd-standard" \
 --disk-size "20" \
 --image-type "COS" \
 --default-max-pods-per-node "30" \
 --metadata disable-legacy-endpoints=true \
 --scopes "https://www.googleapis.com/auth/devstorage.read_only","https://www.googleapis.com/auth/
logging.write","https://www.googleapis.com/auth/monitoring","https://www.googleapis.com/
auth/servicecontrol",
"https://www.googleapis.com/auth/service.management.readonly","https://www.googleapis.com/
auth/trace.append" \
 --preemptible \
 --no-enable-autoupgrade \
 --no-issue-client-certificate \
 --enable-stackdriver-kubernetes \
 --enable-ip-alias \
 --network "projects/poc-microservices-241815/global/networks/default" \
 --subnetwork "projects/poc-microservices-241815/regions/us-central1/subnetworks/default" \
 --addons HorizontalPodAutoscaling,HttpLoadBalancing,Istio \
 --istio-config auth=MTLS_PERMISSIVE \
 --enable-autorepair

Instalación de Istio en cluster de GKE

La instalación de Istio en GKE tan solo consiste en añadir el add-ons de Istio y configurar la seguridad a MTLS_PERMISIVE. Esto permite a los servicios aceptar tráfico cifrado y descifrado y, por defecto, lo envía descifrado.

Gestión de entornos en GKE

Es una práctica recomendada que el entorno de producción esté en un cluster de Kubernetes independiente y el resto de entornos (test, cua, prepro) en otro cluster.

En este post vamos a crear un único cluster con dos entornos (test y cua). Kubernetes permite esa simulación de entornos a través de namespaces.

Para ello, una vez creado el cluster en el paso anterior procedemos a crear los namespaces:

kubectl apply -f - <<EOF
 {
   "apiVersion": "v1",
   "kind": "Namespace",
   "metadata": {
     "name": "test",
     "labels": {
       "name": "test"
     }
   }
 }
EOF

kubectl apply -f - <<EOF
 {
   "apiVersion": "v1",
   "kind": "Namespace",
   "metadata": {
     "name": "cua",
     "labels": {
       "name": "cua"
     }
   }
 }
EOF

De esta manera tendremos en nuestro cluster un namespace ‘test’ y otro llamado ‘cua’ que harán la función de dos entornos independientes.

Como veremos más adelante, los servicios desplegados en un namespace no son visibles desde otro namespace.Ahora, para aprovechar las ventajas que nos ofrece Istio, debemos decirle que todos los contendores desplegados en ese namespace deben ser proxificados.

Para hacer esto posible, Istio utiliza el patrón sidecar. Puedes ver en qué consiste este patrón en este post.

kubectl label namespace test istio-injection=enabled

kubectl label namespace cua istio-injection=enabled

Anatomía del microservicio

Visión general

Todo los microservicios en goodly están realizados con spring-boot. Utilizamos maven como herramienta de construcción maven.

Hemos generado un arquetipo de maven que permite estandarizar toda la estructura de nuestros microservicios.

Nuestro microservicio expone los siguientes endpoints:

Configuración externa

Para desacoplar la configuración del microservicio del propio contenedor utilizamos configmaps. Para ello, usamos spring-cloud-kubernetes, en concreto el módulo spring-cloud-kubernetes-config.

De esta manera, podemos seguir manteniendo el mismo código para cargar la configuración que cuando lo hacíamos a través de ficheros de configuración según profiles de spring.

Para cada entorno tendremos un configmap independiente que configurará las properties que sean necesarias.

k8s_configmap.test.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: employee-ms
  namespace: test
data:
  application.yaml: |-
    environment:
      current: Current environment is kubernetes test

De esta manera cuando nuestro microservicio arranque leerá la configuración de este configmap.

2019-08-12 08:16:36,653 [main] INFO org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration - Located property source: CompositePropertySource {name='composite-configmap', propertySources=[ConfigMapPropertySource {name='configmap.employee-ms.test}]}

Logging

Gestionar los logs en una arquitectura de microservicios y tenerlos centralizados en un único lugar es algo para lo que hay muchas aproximaciones, pero aquí veremos cómo trabajando con GCP todo se simplifica.

En este caso, todos los logs de nuestros microservicios están centralizados en el servicio de Google Stackdriver Logging. De esta manera, podemos acceder rápidamente a ellos y crear métricas en base a la serie de criterios que más nos interese.

Tenemos diferentes niveles de selección posibles:

  1. De tipo de recurso.
  2. De cluster.
  3. De namespace.
  4. De contenedor

Como punto a tener en cuenta, los logs de nuestros microservicios tardan entre 30-60 segundos en estar disponibles en Stackdriver.

Docker y Kubernetes

Cada uno de los microservicios tiene dos ficheros que son imprescindibles.

Dockerfile

ARG JAR_NAME=localimage:latest
FROM alpine

RUN apk update && apk add openjdk8

ENV JAVA_VERSION 1.8
ENV APP_HOME /microservice

RUN mkdir $APP_HOME && adduser -S -D -H java

COPY Docker/scripts $APP_HOME
ADD target/$JAR_NAME $APP_HOME

RUN chown -R java $APP_HOME

WORKDIR $APP_HOME

EXPOSE 8080
USER java

CMD ["./start.sh"]

k8s_manifest.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: employee-ms
  name: employee-ms
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  selector:
    matchLabels:
      run: employee-ms
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      labels:
        run: employee-ms
    spec:
      containers:
        - image: gcr.io/poc-microservices-241815/employee-ms:v1.0.0
          imagePullPolicy: Always
          name: employee-ms
          ports:
            - containerPort: 8080
              protocol: TCP
          resources:
            requests:
              cpu: 500m
              memory: 1024Mi
            limits:
              cpu: 1000m
              memory: 2048Mi
          readinessProbe:
            tcpSocket:
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File

      restartPolicy: Always
      schedulerName: default-scheduler
      terminationGracePeriodSeconds: 30
status: {}

---
apiVersion: v1
kind: Service
metadata:
  labels:
    run: employee-ms
  name: employee-ms-service
spec:
  type: ClusterIP
  selector:
    run: employee-ms
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

Construye y despliega tus microservicios

Lo primero que haremos será generar el empaquetamiento de nuestro microservicio. Al tratarse de una aplicación spring-boot el resultado será un jar.

mvn clean package -DskipTests

Posteriormente, construimos la imagen de docker.

MICROSERVICE_NAME=employee-ms
VERSION=1.0.0

docker build -t $MICROSERVICE_NAME:$VERSION --build-arg JAR_NAME=$MICROSERVICE_NAME-$VERSION.jar -f Dockerfile .

Finalmente, creamos un tag de la imagen y hacemos push para subirla a Google Container Repository.

docker tag $MICROSERVICE_NAME:$VERSION gcr.io/<your-google-projectId>/$MICROSERVICE_NAME:$VERSION

docker push gcr.io/<your-google-projectId>/$MICROSERVICE_NAME:$VERSION

Estamos ya en disposición de desplegar nuestro microservicio y su configuración en el namespace test.

kubectl apply -f k8s_configmap.test.yaml

kubectl apply -f k8s_manifest.yaml -n test

¿Por qué en el configmap no hay que especificar el namespace y en Deployment sí?

En el configmap ya le estamos diciendo internamente, a través de ‘metadata.namespace: test’, que ese configmap va a ese namespace y, sin embargo, el manifest no lo lleva incluido porque queremos que sea válido, tanto para poder desplegar en test como en cua.

Hacer una promoción de entorno de un microservicio que tenemos en test, sería tan sencillo como:

kubectl apply -f k8s_configmap.cua.yaml

kubectl apply -f k8s_manifest.yaml -n cua

Arquitectura según la situación actual:

Haz visibles tus microservicios

Tenemos un microservicio desplegado en dos entornos que no es visible más que a otros microservicios que están desplegados en ese mismo namespace.

¿Qué pasaría si tengo que dar visibilidad hacía el mundo exterior? En este caso, Istio, con todas sus posibilidades de traffic management, nos simplifica la vida.

Por defecto, Istio nos ofrece un servicio ingressgateway que tiene vinculado una IP externa. Ahora, hay que definir cómo, a través de esa IP, llegamos a los microservicios que nos interesen.

Paso 1. Creación de nuestro propio gateway

k8s_istio-global-gateway.yaml

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: goodly-gateway
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway --md-var-hashtag- use Istio default gateway implementation
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

kubectl apply -f k8s_istio-global-gateway.yaml

Paso 2. Creación de Virtual Services

Tenemos que crear tantos virtual services como namespace tengamos que conectan nuestro gateway con los servicios que hay desplegados en cada entorno.

k8s_istio-virtual-service.test.yaml

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: goodly-vs
  namespace: test
spec:
  hosts:
  - anavidad-eval-test.apigee.net --md-var-hashtag-Your domain here
  gateways:
  - goodly-gateway.istio-system.svc.cluster.local
  http:
  - match:
    - uri:
        prefix: /employee-ms
    route:
    - destination:
        host: employee-ms
        port:
          number: 80

kubectl apply -f k8s_istio-virtual-service.test.yaml

Analicemos qué hace este virtual service poco a poco:

Hacemos lo propio para el namespace cua.

k8s_istio-virtual-service.cua.yaml

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: goodly-vs
  namespace: cua
spec:
  hosts:
  - anavidad-eval-cua.apigee.net --md-var-hashtag-Your domain here
  gateways:
  - goodly-gateway.istio-system.svc.cluster.local
  http:
  - match:
    - uri:
        prefix: /employee-ms
    route:
    - destination:
        host: employee-ms
        port:
          number: 80

kubectl apply -f k8s_istio-virtual-service.cua.yaml

De esta manera la arquitectura final queda así:

Gracias a este post hemos visto cómo tener una primera aproximación a una arquitectura de microservicios en GKE.

Hay ciertos puntos que no se han abarcado en este post: la apificación de estos microservicios desplegados en GKE a través de Apigee, donde se definirán las políticas seguridad y de caché, entre otras.

Tampoco la integración continua y el despliegue automático de microservicios en Kubernetes, que es una parte fundamental para hacer que esta arquitectura sea viable. Estos temas los trataremos más adelante en futuros posts.

Cuéntanos qué te parece.

Enviar.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.