My Profile Photo

Mauricio Villagran - Blog


#Cloud #DevOps #Azure #Powershell #Microsoft


Kubernetes & CI/CD App - Capitulo 3

Esta va a ser una serie de posts, en los que como siempre, me mata la curiosidad por seguir aprendiendo del tema. Les voy a mostrar cosas tan simples como desplegar un mini cluster de kubernetes en Windows, tambien veremos como instalar Jenkins en dicho cluster con Helm, y por ultimo armar un pipeline para desplegar una aplicacion. Esta va a ser una serie de tres post los cuales ire subiendo a medida que pueda irlos publicando.

Introducción

A continuacion vamos a:

1- Crear un cluster de Kubernetes en la nube de IBM.

2- Crear una imagen de Jenkins custmizada.

3- Desplegar dicha imagen.

4- Configurar Jenkins.

5- Generar un pipeline.

Crear cluster de K8S en la nube de IBM

Una vez que nos registramos en IBM Cloud, seleccionamos Kubernetes. Luego hacemos un clic en Crear un Cluster y por ultimo seleccionamos Free Plan

IBMCloudConsole1

IBMCloudConsole2

Nota: La creacion del cluster va a demorar mas de 30 minutos.

IBMCloudConsole3

Crear imagen de Jenkins customizada

Jenkins esta diseñado para estar instaldo en una maquina virtual o fisica y no corriendo en un contenedor, por lo cual, lo que haremos a continuacion es crear un Docker File custom, con algunos componentes adicionales.

Crearemos un archivo llamado Dockerfile que tendra lo siguiente.

FROM jenkins/jenkins:lts

USER root

ENV DOCKERVERSION=18.03.1-ce

RUN curl -fsSLO https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKERVERSION}.tgz \
  && tar xzvf docker-${DOCKERVERSION}.tgz --strip 1 \
                 -C /usr/local/bin docker/docker \
  && rm docker-${DOCKERVERSION}.tgz

RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl \
	&& chmod +x ./kubectl \
	&& mv ./kubectl /usr/local/bin/kubectl

Este docker file ejecuta una serie de comandos u ordenes, las cuales analizaremos linea a linea:

La primer linea, especifica la imagen base a usar.

FROM jenkins/jenkins:lts

La segunda linea especifica con que usuario se correran los comandos, se ejecutaran como root, Docker-Cli y Kubectl.

USER root

La tercera linea, especifica una variable de entorno.

ENV DOCKERVERSION=18.03.1-ce

La cuarta linea, baja Docker Cli y remueve el demonio de Docker.

RUN curl -fsSLO https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKERVERSION}.tgz \
  && tar xzvf docker-${DOCKERVERSION}.tgz --strip 1 \
                 -C /usr/local/bin docker/docker \
  && rm docker-${DOCKERVERSION}.tgz

Nota: La imagen custom de Jenkins, no contiene el demonio de Doker. Dicho demonio correra en otro contenedor. Referencia: [Docker in Docker] [Docker in Docker] [Docker in Docker]:https://hub.docker.com/_/docker

La quinta linea, instala kubectl.

RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl \
	&& chmod +x ./kubectl \
	&& mv ./kubectl /usr/local/bin/kubectl

Como mencionaba anteriormente Docker lee linea a linea y ejecuta en orden lo que tenemos en nuestro Dockerfile.

Por ultimo vamos a hacer un build de la imagen y un *push a nuetro registro en Docker Hub.

docker build -t maurivg25/jenkins-custom:latest .

Desplegar imagen custom

La imagen custom se encuentra en nuestro ambiente local y kubernetes no tiene acceso a dicho ambiente. Por lo cual necesitamos pushear dicha imagen a un registro, que puede ser publico o privado. Por ejemplo, Docker-Hub o IBM Container Registry. Por defecto docker utiliza Docker-Hub.

Primer paso, debemos loguearnos en Docker-Hub via terminal.

docker login

DockerLogin01

Una vez que estamos logueados al registro de Docker-Hub, haremos un push de la imagen customizada.

docker push maurivg25/jenkins-custom:latest

Una vez que tenemos la imagen pusheada en el registro de Docker-Hub, vamos a configurar Kubernetes, a traves de archivos de configuracion .yaml.

Lo primero que vamos a hacer es crear un manifiesto llamado jenkins-deployment.yaml. Esto creara un deplyment en kubernetes con nuestro pod de jenkins. Luego modificamos en donde esta la parte de imagenes y apuntamos a nuestro registro, la linea seria: image: maurivg25/jenkins-custom:latest

apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
  labels:
    app: jenkins
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      volumes:
      - name: dind-storage
        emptyDir: {}
      containers:
      - name: jenkins
        image: maurivg25/jenkins-custom:latest
        ports:
        - containerPort: 8080
        - containerPort: 50000
        env:
        - name: DOCKER_HOST
          value: tcp://localhost:2375
      - name: dind
        image: docker:18.05-dind
        securityContext:
          privileged: true
        volumeMounts:
        - name: dind-storage
          mountPath: /var/lib/docker

Por otra parte, necesitamos exponer nuestro acceso a jenkins, para ello lo haremos creando un servicio. A continuacion crearemos un manifiesto llamado jenkins-service.yaml.

apiVersion: v1
kind: Service
metadata:
  name: jenkins-service
spec:
  type: NodePort
  selector:
    app: jenkins
  ports:
  - name: web-interface
    protocol: TCP
    nodePort: 30100
    port: 8080
    targetPort: 8080
  - name: remote-java-api
    protocol: TCP
    nodePort: 30200
    port: 50000
    targetPort: 50000

Como ven en el manifiesto anterior, se especifica que el tipo es Node Port, por el cual se expone un unico puerto en cada nodo del cluster (30200).

Lo siguiente que vamos a hacer, es instalar el CLI de IBM en el equipo donde estamos trabajando. Les dejo el instructivo: [IBM Cloud CLI] [IBM Cloud CLI] [IBM Cloud CLI]:https://cloud.ibm.com/docs/cli?topic=cli-install-ibmcloud-cli

Luego dentro del cluster de Kubernetes en la consola de IBM, vamos a la parte de Acceso.

IBM Cli

Primero ejecutamos el comando de login a IBM.

sudo ibmcloud login -a cloud.ibm.com -r us-south -g Default

Segundo, establecemos el contexto de Kubernetes.

Nota: Antes debemos instalar el plugin para que podamos usar el comando ks

 sudo ibmcloud plugin install container-service
sudo ibmcloud ks cluster config --cluster c4l48nid0t79ru4nah40

Verificamos si nos podemos conectar al cluster de kubernetes usando la cli de IBM.

Nota: La salida de este comando nos dara la ruta local donde descarga toda la configuracion del cluster en nuestra mauqina.

Ejemplo:

OK
The configuration for c4l48nid0t79ru4nah40 was downloaded successfully.

FAILED
Error merging new kubeconfig into the current kubeconfig file: mkdir /Users/user: permission denied

You can manually use the new kubeconfig file: /Users/mvillagran1/.bluemix/plugins/container-service/clusters/clusterk8S-free-c4l48nid0t79ru4nah40/kube-config-aaa00-clusterk8S-free.yml
sudo kubectl config current-context

Por ultimo, descargamos la configuracion del cluster.

export KUBECONFIG=/Users/user/.bluemix/plugins/container-service/clusters/c4l48nid0t79ru4nah40/kube-config.yml 

Lo siguiente que haremos es desplegar del deployment y el servicio en el cluster de Kubernetes, ejecutando los siguientes comandos.

kubectl apply -f jenkins-deployment.yaml
kubectl apply -f jenkins-service.yaml

Para ver si se han creado el deployment, servicio, y pods, ejecutamos el siguiente comando.

kubectl get deployment,pod,service

Kubectl01

Lo siguiente que vamos a hacer es ejecutar los siguientes comandos, los cuales nos daran la ip y puerto del nodo worker, para poder acceder a Jenkins.

export EXTERNAL_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address }')
export NODE_PORT=30100
echo $EXTERNAL_IP:$NODE_PORT

Al ejecutar el ulimo comando, nos dara la ip y puerto para acceder a jenkins desde un navegador.

169.57.85.101:30100

Configurar Jenkins

Ahora vamos a configurar Jenkins, el usuario admin inicial, su password, etc. A continuacion vamos a ver los logs del contenedor, para ver la password inicial.

kubectl logs $(kubectl get pods --selector=app=jenkins -o=jsonpath='{.items[0].metadata.name}') jenkins

Kubectl01

Una vez que hayamos puesto la password inicial obtenida con el comando anterior, vamos a instalar los plugins sugeridos, seleccionar continuar como admin, y por ultimo clic en el boton save and finish.

Cuando tengamos Jenkins listo, vamos a necesitar tener las siguientes credenciales configuradas.

GitHub son las credenciales para acceder al repositorio de codigo.

DockerHub son las credenciales para subir las imagenes al registro de Docker Hub.

Kubeconfig es el archivo de configuracion el cual contiene las credenciales para acceder al cluster de K8S.

GitHub y DockerHub son credenciales que usan Username with password. Pero Kubeconfig son credenciales que se encuentran dentro de un Secret file. Recordar que en el paso 3 se habia descargado el archivo kubeconfig y se habia exportado una variable de entorno en nuestra computadora.

Ahora necesitamos ir al directorio donde esta la configuracion de Kubeconfig

Podemos ejecutar el siguiente comando para ver el archivo de configuracion:

cd /Users/mvillagran1/.bluemix/plugins/container-service/clusters/clusterk8S-free-c4l48nid0t79ru4nah40/

En el directorio anterior, eberiamos tener los siguientes archivos (Se los comparto con la definicion oficial):

<PEM-FILE>.pem archivo que significa Privacy-Enhanced Mail y es un formato de archivo para almacenar y enviar claves criptográficas, certificados y otros datos.

<KUBE-CONFIG>.yml El archivo se utiliza para configurar el acceso a un clúster y, a veces, se denomina archivo kubeconfig, que es una forma genérica de referirse a los archivos de configuración.

Ambos archivos deben estar en el mismo directorio. En Jenkins, no hay ninguna opción para mantener estos dos archivos en el mismo directorio. Por esta razón, el archivo <PEM-FILE> .pem debe estar incrustado en el archivo <KUBE-CONFIG> .yml. Para hacer esto, copiamos ambos archivos en el directorio del escritorio. El proceso de copia no es obligatorio pero lo hacemos para preservar los archivos originales.

for file in ./*; do cp $file /Users/$USER/Desktop; done;

Ahora, vamos al escritorio o desktop y tenemos que codificar en base64 el archivo ‘.pem'

Ejecutamos el siguiente comando:

base64 <PEM-FILE>.pem

En mi caso:

base64 ca-aaa00-clusterk8S-free.pem

Ahora debemos abrir el archivo Kube-Config.yml que esta en el escritorio y pegar el resultado del pasaje a base64

Tenemos que buscar en donde dice certificate-authority: <PEM-FILE>.pem y cambiarlo por certificate-authority-data: <BASE64-RESULT>

Ahora vamos al menu de Jenkins debemos ir a Administrar Jenkins, luego dentro de Security, vamos a Manage Credentials y dentro del store scope, seleccionamos Global clic en la “flecha” y por ultimo Add Credentals.

Primero vamos a agregar las credenciales de Github especificando usuario y password, con el ID github. Luego vamos a agregar las de Dockerhub tambien con usuario y password, con el ID dockerhub. Por utimo agregaremos las credenciales de kubeconfig seleccionando la opcion de secret file, con ID kubeconfig.

Una vez que tenemos las credenciales cargadas, vamos a la parte de administracion de plugins, e instalamos el plugin llamado Kubernetes CLI

Creacion de un pipeline

Vamos a crear un pipeline que va a crear una app de ejemplo. A continuacion les voy indicando los pasos.

Paso 1 - En el menu de la izquierda, hacer clic en nueva tarea. Luego ingresamos el nombre del proyecto o pipeline, por ultimo seleccionamos la opcion Pipeline, clic en Ok.

JenkinsPipeline01

Paso 2 - En las opciones del menu General, seleccionamos la opcion GitHub project. Luego ingresamos la url del proyecto que tenemos en Github. (Ejemplo: https://github.com/maurivg28/jenkins_demo). Yo en esa url tengo todos los templates, codigo, etc.

JenkinsPipeline02

Paso 3 - Vamos a la opcion Pipeline, en la opcion Definition, seleccionamos Pipeline script from SCM. Luego en la opcion SCM, seleccionamos Git. Luego en la parte llamada Repositories, ingresamos la url del proyecto (Ejemplo: https://github.com/maurivg28/jenkins_demo.git). Por ultimo seleccionamos la credencial que habiamos configurado en pasos anteriores.

Paso 4 - Dentro del repositorio de nuestro proyecto, vamos a crear un archivo llamado Jenkinsfile (Nota: el archivo no lleva extension)

Dicho archivo va a tener lo siguiente:


pipeline {
  agent any
  stages {
    stage('Docker Build') {
      steps {
        sh "docker build -t maurivg25/podinfo:${env.BUILD_NUMBER} ."
      }
    }
    stage('Docker Push') {
      steps {
        withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
          sh "docker login -u ${env.dockerHubUser} -p ${env.dockerHubPassword}"
          sh "docker push maurivg25/podinfo:${env.BUILD_NUMBER}"
        }
      }
    }
    stage('Docker Remove Image') {
      steps {
        sh "docker rmi maurivg25/podinfo:${env.BUILD_NUMBER}"
      }
    }
    stage('Apply Kubernetes Files') {
      steps {
          withKubeConfig([credentialsId: 'kubeconfig']) {
          sh 'cat deployment.yaml | sed "s//$BUILD_NUMBER/g" | kubectl apply -f -'
          sh 'kubectl apply -f _service.yaml'
        }
      }
  }
}
}

Ahora paso a explicar lo que va a hacer nuestro Jenkins File, pero antes les cuento como esta compuesta la app.

Es una app sencilla en java, lo que hace es traer informacion del pod. Los archivos con index.js que es la app en si, luego un Dockerfile que sera nuestro manifiesto para construir la imagen, y luego un archivo de deployment y otro llamado service que como habiamos visto con el ejemplo de Jenkins, uno genera dicho deployment en Kubernetes con el pod y su contenedor y el otro expone el puerto para acceder.

Descripcion del Jeninsfile:

Este paso lo que hace es invocar un sh de linux, con el comando docker build -t para construir la imagen.

stage('Docker Build') {
      steps {
        sh "docker build -t maurivg25/podinfo:${env.BUILD_NUMBER} ."
      }
    }

Luego este segundo paso, se loguea al registro de docker hub, y hace un push hacia el registro de nuestra imagen.

stage('Docker Push') {
      steps {
        withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
          sh "docker login -u ${env.dockerHubUser} -p ${env.dockerHubPassword}"
          sh "docker push maurivg25/podinfo:${env.BUILD_NUMBER}"
        }
      }
    }

El tercer paso es remover la imagen que hemos construido localmente en jenkins.

stage('Docker Remove Image') {
      steps {
        sh "docker rmi kmlaydin/podinfo:${env.BUILD_NUMBER}"
      }
    }

Por ultimo, ejecuta los comandos para desplegar el deployment y el service en nuetstro cluster de Kubernetes.

  stage('Apply Kubernetes Files') {
      steps {
          withKubeConfig([credentialsId: 'kubeconfig']) {
          sh 'cat deployment.yaml | sed "s//$BUILD_NUMBER/g" | kubectl apply -f -'
          sh 'kubectl apply -f service.yaml'
        }
      }
  }
}

Luego vamos a tener el archivo deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: podinfo-deployment
  labels:
    app: podinfo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: podinfo
  template:
    metadata:
      labels:
        app: podinfo
    spec:
      containers:
      - name: podinfo
        image: maurivg25/podinfo:
        imagePullPolicy: Always
        ports:
        - containerPort: 4444
        env:
        - name: MY_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: MY_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: MY_POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: MY_POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: MY_POD_SERVICE_ACCOUNT
          valueFrom:
            fieldRef:
              fieldPath: spec.serviceAccountName

Otro archivo service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: podinfo-service
spec:
  type: NodePort
  selector:
    app: podinfo
  ports:
  - protocol: TCP
    nodePort: 30300
    port: 4444
    targetPort: 4444

Y por ultimo el archivo Dockerfile:

FROM node:10-alpine

RUN mkdir /app
COPY index.js /app
WORKDIR /app
RUN npm install express
EXPOSE 4444

CMD ["node", "index.js"]

Por ultimo veremos las ejecuciones de nuetro pipeline.

JenkinsPipeline03

Integracion de Jenkins y GitHub

Podemos configurar para que en Jenkins se disparen ciertas acciones que realizamos en Github. La mas clasica es que cuando hacemos commit al repo, se dispare un pipeline. Esto lo hacemos mediante Webhooks.

Para configurar esto, debemos hacer lo siguiente:

1- Ir al proyecto en Github.

2- Clic en Settings y buscar la opcion Webhooks.

3- Clic en Add Webhook.

4- En la opcion URL ingresar la url de nuestro Jenkins, **http://:/github-webhook/** que en mi caso sera **http://169.57.85.101:30100/github-webhook/**.

5- Salvamos la configuracion.

Luego del lado de Jenins, debemos ejecutar los siguientes pasos:

1- Clic en el pipeline. 2- En el menu, ir a Configure. 3- Elegir la opcion GitHub hook trigger for GITScm polling que se encuentra en la parte de Build Triggers. 4- Salvar la configuracion.

Nota: El pipeline se debe de ejecutar manualmente una sola vez para que Jeninks identifique la configuracion que hemos realizado.