Despliegue continuo con Terraform y Kubernetes
Introducción
En este artículo continuaremos donde lo dejamos la última vez Integración continua con Go usando Travis CI y Docker. Los archivos utilizados aquí pueden encontrarse AQUÍ. Crearemos nuestro clúster en DigitalOcean con un balanceador de carga, generaremos el archivo kubeconfig
basado en los certificados proporcionados por Terraform en Travis, y finalmente crearemos un despliegue básico para validar que todo funciona.
DigitalOcean
Necesitamos crear un token para que Terraform pueda crear recursos utilizando la API de DO. Dirígete a tu cuenta, luego en el menú de la izquierda haz clic en API, deberías ver algo como esto: Una vez allí, haz clic en “generate token” (dale un nombre significativo para ti), y asegúrate de que tenga permisos de escritura.
Terraform
El siguiente paso es configurar el token para Terraform. Examinemos todos los archivos y veamos qué harán, pero primero proporcionaremos los secretos a nuestra aplicación a través de variables de entorno. Yo he encontrado útil usar direnv
en muchos proyectos, por lo que el contenido del primer archivo .envrc
se vería algo así:
export TF_VAR_DO_TOKEN=insertar_tu_token_aquí
Después de eso, necesitarás permitir su ejecución ejecutando direnv allow
.
El primer archivo de Terraform que vamos a revisar es provider.tf
:
# Configura el proveedor de DigitalOcean con su token
variable "DO_TOKEN" {}
provider "digitalocean" {
token = "${var.DO_TOKEN}"
}
Como estamos utilizando variables de entorno, necesitamos declararlo y luego establecerlo en el proveedor. Por ahora, solo necesitamos el token.
Luego, el archivo kubernetes.tf
:
# Crear el clúster
resource "digitalocean_kubernetes_cluster" "dev-k8s" {
name = "dev-k8s"
region = "nyc1"
version = "1.14.1-do.2"
node_pool {
name = "dev-k8s-nodes"
size = "s-1vcpu-2gb"
node_count = 1
tags = ["dev-k8s-nodes"]
}
}
Este archivo será el responsable de crear el clúster de Kubernetes. Como es nuestro clúster de desarrollo, solo necesitamos un nodo.
El siguiente archivo es lb.tf
:
# Crear un balanceador de carga asociado a nuestro clúster
resource "digitalocean_loadbalancer" "public" {
name = "loadbalancer-1"
region = "nyc1"
forwarding_rule {
entry_port = 80
entry_protocol = "http"
target_port = 30000
target_protocol = "http"
}
healthcheck {
port = 30000
protocol = "tcp"
}
droplet_tag = "dev-k8s-nodes"
}
Este archivo es particularmente interesante porque proporcionará un punto de acceso a nuestras aplicaciones (puerto 80 en su dirección IP pública), y también usa una verificación de estado básica.
Y finalmente, el archivo output.tf
:
# Exportar el archivo de configuración de kubectl
resource "local_file" "kubernetes_config" {
content = "${digitalocean_kubernetes_cluster.dev-k8s.kube_config.0.raw_config}"
filename = "kubeconfig.yaml"
}
# Imprimir la IP del balanceador de carga
output "digitalocean_loadbalancer" {
value = "${digitalocean_loadbalancer.public.ip}"
description = "La dirección IP pública del balanceador de carga."
}
Este archivo imprimirá el archivo de configuración de Kubernetes que necesitamos para poder usar kubectl
, y también la dirección IP del balanceador de carga.
¿Qué hacemos con todo esto? Primero necesitarás ejecutar terraform init
dentro de la carpeta de terraform para descargar los plugins y proveedores. Una vez hecho esto, puedes ejecutar terraform plan
para ver qué cambios desea hacer terraform o terraform apply
para aplicarlos. ¿Cómo se verá esto?:
# Inicializar terraform
$ terraform init
...
# Aplicar
$ terraform apply
...
Esto creará nuestro clúster en DigitalOcean. Recuerda destruirlo después de usarlo con terraform destroy
. Si no usas un plan, se te pedirá confirmación cuando ejecutes terraform apply
; revisa y di sí
.
Travis
Hicimos algunas adiciones a nuestro archivo .travis.yml
, que son principalmente para preparar kubectl
y también para desencadenar un despliegue si la compilación tiene éxito.
language: go
services:
- docker
before_install:
- docker build --no-cache -t ${TRAVIS_REPO_SLUG}:${TRAVIS_COMMIT} .
- docker run ${TRAVIS_REPO_SLUG}:${TRAVIS_COMMIT} /go/src/github.com/kainlite/whatismyip-go/whatismyip-go.test
- docker run -d -p 127.0.0.1:8000:8000 ${TRAVIS_REPO_SLUG}:${TRAVIS_COMMIT}
script:
- curl 127.0.0.1:8000
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
- docker push ${TRAVIS_REPO_SLUG}:${TRAVIS_COMMIT}
after_success:
- echo ${KUBERNETES_CA} | base64 -d > k8s-ca.pem
- echo ${KUBERNETES_CLIENT_CA} | base64 -d > k8s-client-ca.pem
- echo ${KUBERNETES_CLIENT_KEY} | base64 -d > k8s-key.pem
- curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
- envsubst < ./kubernetes/manifest.yml.template > ./kubernetes/manifest.yml
- chmod u+x ./kubectl
- ./kubectl config set-cluster dev-k8s --server=${KUBERNETES_ENDPOINT} --certificate-authority=k8s-ca.pem
- ./kubectl config set-credentials dev-k8s --client-certificate=k8s-client-ca.pem --client-key=k8s-key.pem
- ./kubectl config set-context dev-k8s --cluster=dev-k8s --namespace=default --user=dev-k8s
- ./kubectl config use-context dev-k8s
- ./kubectl apply -f ./kubernetes/manifest.yml
Como se muestra en la imagen, tomamos los certificados codificados en base64 y los cargamos en Travis como variables de entorno (KUBERNETES_CA, KUBERNETES_CLIENT_CA, KUBERNETES_CLIENT_KEY, KUBERNETES_ENDPOINT), luego los decodificamos en archivos, creamos la configuración usando kubectl
y la activamos, y luego aplicamos el despliegue con el hash generado.
Así es como debería verse en Travis:
Veamos la configuración generada de Kubernetes y qué valores debes tener en cuenta:
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
...
server: https://592d58c9-98c1-4285-b098-cbc9378e9f89.k8s.ondigitalocean.com
name: do-nyc1-dev-k8s
contexts:
- context:
cluster: do-nyc1-dev-k8s
user: do-nyc1-dev-k8s-admin
name: do-nyc1-dev-k8s
current-context: do-nyc1-dev-k8s
kind: Config
preferences: {}
users:
- name: do-nyc1-dev-k8s-admin
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
...
Nunca hagas esto, no compartas tu configuración o cualquiera podrá usar tu clúster. También ten cuidado de no comprometerlo en tu repositorio. En este ejemplo ya no es válido porque, después de ejecutar los ejemplos, destruí el clúster con terraform destroy
. Ahora, hay cuatro valores de interés para nosotros: certificate-authority-data: KUBERNETES_CA
, client-certificate-data: KUBERNETES_CLIENT_CA
, client-key-data: KUBERNETES_CLIENT_KEY
, y server: KUBERNETES_ENDPOINT
. Con estas variables, podemos recrear fácilmente nuestra configuración de Kubernetes usando kubectl
. Ten en cuenta que no estamos dec
odificando para guardarlo en Travis; lo hacemos en el archivo de configuración de Travis (.travis.yml
).
Kubernetes
Después de todo eso, aún necesitamos tener una plantilla de despliegue para desplegar nuestra aplicación, y es una plantilla porque necesitamos reemplazar el SHA de la compilación actual en el manifiesto antes de enviarlo a la API de Kubernetes. Revisemos el archivo manifest.yml.template
:
---
apiVersion: v1
kind: Service
metadata:
name: whatismyip-go-service
spec:
type: NodePort
ports:
- port: 8000
targetPort: 8000
nodePort: 30000
selector:
app: whatismyip-go
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: whatismyip-go-deployment
namespace: default
labels:
app: whatismyip-go
spec:
replicas: 1
selector:
matchLabels:
app: whatismyip-go
template:
metadata:
labels:
app: whatismyip-go
spec:
containers:
- name: whatismyip-go
image: kainlite/whatismyip-go:${TRAVIS_COMMIT}
ports:
- containerPort: 8000
name: http
Aquí exponemos nuestro servicio en el puerto 30000 como un NodePort y desplegamos el SHA actual (reemplazado durante la ejecución por Travis).
Probando todo
Valida que el despliegue se haya realizado correctamente verificando nuestro clúster de Kubernetes:
$ kubectl get pods --kubeconfig=./kubeconfig.yaml
NAME READY STATUS RESTARTS AGE
whatismyip-go-deployment-5ff9894d8c-f48nx 1/1 Running 0 9s
Primero probamos el balanceador de carga, y como veremos, la IP no es correcta, es la IP interna del balanceador de carga y no nuestra dirección IP pública.
$ curl -v 159.203.156.153
* Trying 159.203.156.153...
* TCP_NODELAY set
* Connected to 159.203.156.153 (159.203.156.153) port 80 (#0)
> GET / HTTP/1.1
> Host: 159.203.156.153
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 05 May 2019 21:14:28 GMT
< Content-Length: 12
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host 159.203.156.153 left intact
10.136.5.237* Closing connection 0
Pero si accedemos directamente a nuestro servicio, podemos ver la dirección IP correcta, esto podría mejorarse pero lo dejamos como ejercicio para el lector ◕_◕.
$ curl -v 142.93.207.200:30000
* Trying 142.93.207.200...
* TCP_NODELAY set
* Connected to 142.93.207.200 (142.93.207.200) port 30000 (#0)
> GET / HTTP/1.1
> Host: 142.93.207.200:30000
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 05 May 2019 21:35:13 GMT
< Content-Length: 12
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host 142.93.207.200 left intact
111.138.53.63* Closing connection 0
Finalmente, verifiquemos lo que deberíamos ver en Travis:
Como podemos ver, todo salió bien y nuestro despliegue se aplicó con éxito en nuestro clúster.
Notas finales
Publicaré algunos artículos sobre CI y CD y buenas prácticas que los DevOps/SRE deben tener en cuenta, consejos, trucos y ejemplos completos de despliegue. Este es la segunda parte de una posible serie de tres artículos (el siguiente debería tratar lo mismo pero usando Jenkins) con un ejemplo básico pero completo de CI y luego CD. Esto, por supuesto, puede cambiar y cualquier comentario será muy apreciado :).
En este ejemplo, muchas cosas podrían mejorarse. Por ejemplo, usamos un puerto de nodo y no hay un firewall, por lo que podemos acceder a nuestra aplicación directamente a través del NodePort o usando el balanceador de carga. Deberíamos agregar algunas reglas de firewall para que solo el balanceador de carga pueda comunicarse con el rango de puertos del NodePort (30000-32767).
También ten en cuenta que para producción, esta configuración no será suficiente, pero para un entorno de desarrollo sería suficiente inicialmente.
Algunos enlaces útiles para travis y terraform.
Errata
Si encuentras algún error o tienes alguna sugerencia, por favor envíame un mensaje para que se corrija.
No tienes cuenta? Regístrate aqui
Ya registrado? Iniciar sesión a tu cuenta ahora.
-
Comentarios
Online: 0
Por favor inicie sesión para poder escribir comentarios.