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
Nota: La creacion del cluster va a demorar mas de 30 minutos.
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
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.
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
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
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 ‘
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
.
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.
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.
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://
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.