Primeros pasos con ksonnet


Introducción

Este tutorial te mostrará cómo crear una aplicación simple y también cómo desplegarla en Kubernetes usando ksonnet. En los ejemplos, usaré minikube o puedes consultar este repositorio que tiene una buena visión general de minikube. Una vez instalado y arrancado (minikube start), ese comando descargará y configurará el entorno local. Si has estado siguiendo las publicaciones anteriores, ya tienes minikube instalado y funcionando. Antes de sumergirnos en un ejemplo, revisemos alguna terminología de ksonnet (extraída de la documentación oficial):


Aplicación

Una aplicación ksonnet representa un directorio bien estructurado de manifiestos de Kubernetes (esto se genera usando ks init).


Entorno

Un entorno consiste en cuatro elementos, algunos de los cuales pueden obtenerse de tu contexto kubeconfig actual: Nombre, Servidor, Espacio de nombres, Versión de API. El entorno determina a qué clúster vas a desplegar la aplicación.


Componente

Un componente puede ser tan simple como un recurso de Kubernetes (un Pod, Deployment, etc.), o una pila completamente funcional, por ejemplo EFK/ELK. Puedes generar componentes usando ks generate.


Prototipo

Prototipo + Parámetros = Componente. Piensa en un prototipo como una plantilla base antes de aplicar los parámetros, para establecer un nombre, réplicas, etc., para el recurso. Puedes explorar algunos prototipos del sistema con ks prototype.


Parámetro

Da vida a un componente con valores dinámicos. Puedes usar ks param para ver o modificar parámetros. Hay parámetros de Aplicación (globales), parámetros de Componente y parámetros de Entorno (anulan los parámetros de la aplicación).


Módulo

Los módulos proporcionan una forma de compartir componentes entre entornos. Más concisamente, un módulo se refiere a un subdirectorio en components/ que contiene su propio params.libsonnet. Para crear un módulo: ks module create <nombre_del_módulo>.


Parte

Proporciona una forma de organizar y reutilizar código.


Paquete

Un paquete es un conjunto de prototipos relacionados y bibliotecas auxiliares asociadas. Te permite crear y compartir paquetes entre aplicaciones.


Registro

Es esencialmente un repositorio para paquetes; soporta el registro de incubadora, GitHub, sistema de archivos y Helm.


Manifiesto

El mismo antiguo manifiesto YAML o JSON pero esta vez escrito en Jsonnet; básicamente, Jsonnet es una extensión simple de JSON.


Uf, son muchos nombres y terminología a la vez. Comencemos ya con el terminal.


Empecemos

Este comando generará la siguiente estructura de carpetas ks init wordpress:

INFO Using context "minikube" from kubeconfig file "~/.kube/config"
INFO Creating environment "default" with namespace "default", pointing to "version:v1.12.4" cluster at address "https://192.168.99.100:8443"
INFO Generating ksonnet-lib data at path '~/k8s-examples/wordpress/lib/ksonnet-lib/v1.12.4'

$ ls -l |  awk '{ print $9 }'
app.yaml        <--- Define versiones, espacio de nombres, dirección del clúster, nombre de la aplicación, registro.
components      <--- Componentes; por defecto está vacío y tiene un archivo de parámetros.
environments    <--- Por defecto solo hay un entorno llamado default.
lib             <--- Aquí podemos encontrar los ayudantes de ksonnet que coinciden con la API de Kubernetes con los recursos comunes (Pods, Deployments, etc.).
vendor          <--- Aquí es donde se instalan los paquetes/aplicaciones; puede verse como una carpeta de dependencias.

Generemos un deployed-service e inspeccionemos su contexto:

$ ks generate deployed-service wordpress \
      --image bitnami/wordpress:5.0.2 \
      --type ClusterIP

INFO Writing component at '~/k8s-examples/wordpress/components/wordpress.jsonnet'

Al momento de escribir esto, la última versión de WordPress es 5.0.2. Siempre se recomienda usar números de versión estáticos en lugar de etiquetas como ‘latest’ (porque ‘latest’ puede no ser el más reciente).


Veamos cómo se ve nuestro componente:

local env = std.extVar("__ksonnet/environments");
local params = std.extVar("__ksonnet/params").components.wordpress;
[
  {
    "apiVersion": "v1",
    "kind": "Service",
    "metadata": {
      "name": params.name
    },
    "spec": {
      "ports": [
        {
          "port": params.servicePort,
          "targetPort": params.containerPort
        }
      ],
      "selector": {
        "app": params.name
      },
      "type": params.type
    }
  },
  {
    "apiVersion": "apps/v1beta2",
    "kind": "Deployment",
    "metadata": {
      "name": params.name
    },
    "spec": {
      "replicas": params.replicas,
      "selector": {
        "matchLabels": {
          "app": params.name
        },
      },
      "template": {
        "metadata": {
          "labels": {
            "app": params.name
          }
        },
        "spec": {
          "containers": [
            {
              "image": params.image,
              "name": params.name,
              "ports": [
                {
                  "containerPort": params.containerPort
                }
              ]
            }
          ]
        }
      }
    }
  }
]

Es solo otra plantilla para algunos recursos conocidos, un Service y un Deployment; de ahí proviene el nombre: deployed-service. Pero, ¿de dónde vienen esos parámetros?


Si ejecutamos ks show default:

---
apiVersion: v1
kind: Service
metadata:
  labels:
    ksonnet.io/component: wordpress
  name: wordpress
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: wordpress
  type: ClusterIP
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  labels:
    ksonnet.io/component: wordpress
  name: wordpress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wordpress
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
      - image: bitnami/wordpress:5.0.2
        name: wordpress
        ports:
        - containerPort: 80

Veremos lo que nuestro paquete generará en YAML con algunos buenos valores predeterminados. Y por defecto, si recuerdas de las definiciones, un componente necesita un archivo de parámetros para llenar los espacios en blanco; en este caso es components/params.libsonnet:

{
  global: {
    // Parámetros globales definidos por el usuario; accesibles para todos los componentes y entornos, Ej:
    // replicas: 4,
  },
  components: {
    // Parámetros a nivel de componente, definidos inicialmente desde 'ks prototype use ...'
    // Cada objeto a continuación debería corresponder a un componente en el directorio components/
    wordpress: {
      containerPort: 80,
      image: "bitnami/wordpress:5.0.2",
      name: "wordpress",
      replicas: 1,
      servicePort: 80,
      type: "ClusterIP",
    },
  },
}

Pero eso no es suficiente para ejecutar WordPress, ¿verdad? No, no lo es. Necesitamos una base de datos con almacenamiento persistente para que funcione correctamente, así que necesitaremos generar y extender otro deployed-service.


El siguiente paso sería crear otro componente:

$ ks generate deployed-service mariadb \
      --image bitnami/mariadb:10.1.37 \
      --type ClusterIP

INFO Writing component at '/home/kainlite/Webs/k8s-examples/wordpress/components/mariadb.jsonnet'

La última versión estable de MariaDB 10.1 GA al momento de escribir esto es 10.1.37.


Luego necesitaremos agregar un volumen persistente y también decirle a WordPress que use esta instancia de MariaDB. ¿Cómo hacemos eso? Necesitaremos modificar algunos archivos, de esta manera (para reutilizar cosas, coloqué las variables de MySQL en la sección global; para este ejemplo eso simplificará las cosas, pero podría no ser el mejor enfoque para un entorno de producción):

El components/params.json resultante será:

{
  global: {
    // Parámetros globales definidos por el usuario; accesibles para todos los componentes y entornos, Ej:
    // replicas: 4,
    mariadbEmptyPassword: "no",
    mariadbUser: "mywordpressuser",
    mariadbPassword: "mywordpresspassword",
    mariadbDatabase: "bitnami_wordpress",
  },
  components: {
    // Parámetros a nivel de componente, definidos inicialmente desde 'ks prototype use ...'
    // Cada objeto a continuación debería corresponder a un componente en el directorio components/
    wordpress: {
      containerPort: 80,
      image: "bitnami/wordpress:5.0.2",
      name: "wordpress",
      replicas: 1,
      servicePort: 80,
      type: "ClusterIP",
    },
    mariadb: {
      containerPort: 3306,
      image: "bitnami/mariadb:10.1.37",
      name: "mariadb",
      replicas: 1,
      servicePort: 3306,
      type: "ClusterIP",
    },
  },
}

El components/wordpress.jsonnet resultante será:

local env = std.extVar("__ksonnet/environments");
local params = std.extVar("__ksonnet/params").components.wordpress;
[
  {
    "apiVersion": "v1",
    "kind": "Service",
    "metadata": {
      "name": params.name
    },
    "spec": {
      "ports": [
        {
          "port": params.servicePort,
          "targetPort": params.containerPort
        }
      ],
      "selector": {
        "app": params.name
      },
      "type": params.type
    }
  },
  {
    "apiVersion": "apps/v1beta2",
    "kind": "Deployment",
    "metadata": {
      "name": params.name
    },
    "spec": {
      "replicas": params.replicas,
      "selector": {
        "matchLabels": {
          "app": params.name
        },
      },
      "template": {
        "metadata": {
          "labels": {
            "app": params.name
          }
        },
        "spec": {
          "containers": [
            {
              "image": params.image,
              "name": params.name,
              "ports": [
                {
                  "containerPort": params.containerPort
                }
              ],
              "env": [
                {
                    "name": "WORDPRESS_DATABASE_USER",
                    "value": params.mariadbUser,
                },
                {
                    "name": "WORDPRESS_DATABASE_PASSWORD",
                    "value": params.mariadbPassword,
                },
                {
                    "name": "WORDPRESS_DATABASE_NAME",
                    "value": params.mariadbDatabase,
                },
                {
                    "name": "WORDPRESS_HOST",
                    "value": "mariadb",
                }
              ]
            }
          ]
        }
      }
    }
  }
]

Lo único que cambió aquí es spec.containers.env, que no estaba presente antes.


El components/mariadb.jsonnet resultante será:

local env = std.extVar("__ksonnet/environments");
local params = std.extVar("__ksonnet/params").components.mariadb;
[
{
    "apiVersion": "v1",
        "kind": "Service",
        "metadata": {
            "name": params.name
        },
        "spec": {
            "ports": [
            {
                "port": params.servicePort,
                "targetPort": params.containerPort
            }
            ],
            "selector": {
                "app": params.name
            },
            "type": params.type
        }
},
{
    "apiVersion": "apps/v1beta2",
    "kind": "Deployment",
    "metadata": {
        "name": params.name
    },
    "spec": {
        "replicas": params.replicas,
        "selector": {
            "matchLabels": {
                "app": params.name
            },
        },
        "template": {
            "metadata": {
                "labels": {
                    "app": params.name
                }
            },
            "spec": {
                "containers": [
                {
                    "image": params.image,
                    "name": params.name,
                    "ports": [
                    {
                        "containerPort": params.containerPort
                    },
                    ],
                    "env": [
                    {
                        "name": "ALLOW_EMPTY_PASSWORD",
                        "value": params.mariadbEmptyPassword,
                    },
                    {
                        "name": "MARIADB_USER",
                        "value": params.mariadbUser,
                    },
                    {
                        "name": "MARIADB_PASSWORD",
                        "value": params.mariadbPassword,
                    },
                    {
                        "name": "MARIADB_ROOT_PASSWORD",
                        "value": params.mariadbPassword,
                    },
                    {
                        "name": "MARIADB_DATABASE",
                        "value": params.mariadbDatabase,
                    },
                    ],
                    "volumeMounts": [
                    {
                        "mountPath": "/var/lib/mysql",
                        "name": "mariadb"
                    }
                    ]
                }
                ],
                "volumes": [
                {
                    "name": "mariadb",
                    "hostPath": {
                        "path": "/home/docker/mariadb-data"
                    }
                }
                ]
            }
        }
    }
}
]

Lo sé, lo sé, eso es mucho JSON. Confío en que tienes un buen scroll :).


Las únicas cosas que cambiaron aquí son spec.containers.env, spec.containers.volumeMount y spec.volumes, que no estaban presentes antes. Eso es todo lo que necesitas para hacer que WordPress funcione con MariaDB.


Esta publicación solo rasca la superficie de lo que Ksonnet y Jsonnet pueden hacer. En otra publicación describiré características más avanzadas con menos JSON / YAML. Hay muchas cosas que se pueden mejorar y cubriremos esas cosas en la siguiente publicación. Si deseas ver todo el código fuente de esta publicación, ve aquí.


Limpiemos con ks delete default:

INFO Deleting services mariadb
INFO Deleting deployments mariadb
INFO Deleting services wordpress
INFO Deleting deployments wordpress

Notas

Si deseas verificar la instalación de WordPress a través del navegador, puedes hacer minikube proxy y luego acceder a la siguiente URL: WordPress (estoy usando el espacio de nombres por defecto aquí y el nombre del servicio es wordpress; si usas ingress no necesitas hacer este paso).


No estoy al tanto de si Ksonnet soporta lanzamientos y rollbacks como Helm, pero parece que podría emularse usando etiquetas de git y algunos hooks de git.


Si todo va bien, deberías ver algo como esto en los logs:

$ kubectl logs -f wordpress-5b4d6bd47c-bdtmw

Welcome to the Bitnami wordpress container
Subscribe to project updates by watching https://github.com/bitnami/bitnami-docker-wordpress
Submit issues and feature requests at https://github.com/bitnami/bitnami-docker-wordpress/issues

nami    INFO  Initializing apache
apache  INFO  ==> Patching httpoxy...
apache  INFO  ==> Configuring dummy certificates...
nami    INFO  apache successfully initialized
nami    INFO  Initializing php
nami    INFO  php successfully initialized
nami    INFO  Initializing mysql-client
nami    INFO  mysql-client successfully initialized
nami    INFO  Initializing libphp
nami    INFO  libphp successfully initialized
nami    INFO  Initializing wordpress
mysql-c INFO  Trying to connect to MySQL server
mysql-c INFO  Found MySQL server listening at mariadb:3306
mysql-c INFO  MySQL server listening and working at mariadb:3306
wordpre INFO
wordpre INFO  ########################################################################
wordpre INFO   Installation parameters for wordpress:
wordpre INFO     First Name: FirstName
wordpre INFO     Last Name: LastName
wordpre INFO     Username: user
wordpre INFO     Password: **********
wordpre INFO     Email: user@example.com
wordpre INFO     Blog Name: User's Blog!
wordpre INFO     Table Prefix: wp_
wordpre INFO   (Passwords are not shown for security reasons)
wordpre INFO  ########################################################################
wordpre INFO
nami    INFO  wordpress successfully initialized
INFO  ==> Starting wordpress...
[Thu Dec 27 04:30:59.684053 2018] [ssl:warn] [pid 116] AH01909: localhost:443:0 server certificate does NOT include an ID which matches the server name
[Thu Dec 27 04:30:59.684690 2018] [ssl:warn] [pid 116] AH01909: localhost:443:0 server certificate does NOT include an ID which matches the server name
[Thu Dec 27 04:30:59.738783 2018] [ssl:warn] [pid 116] AH01909: localhost:443:0 server certificate does NOT include an ID which matches the server name
[Thu Dec 27 04:30:59.739701 2018] [ssl:warn] [pid 116] AH01909: localhost:443:0 server certificate does NOT include an ID which matches the server name
[Thu Dec 27 04:30:59.765798 2018] [mpm_prefork:notice] [pid 116] AH00163: Apache/2.4.37 (Unix) OpenSSL/1.1.0j PHP/7.2.13 configured -- resuming normal operations
[Thu Dec 27 04:30:59.765874 2018] [core:notice] [pid 116] AH00094: Command line: 'httpd -f /bitnami/apache/conf/httpd.conf -D FOREGROUND'
172.17.0.1 - - [27/Dec/2018:04:31:00 +0000] "GET / HTTP/1.1" 200 3718
172.17.0.1 - - [27/Dec/2018:04:31:01 +0000] "GET /wp-includes/js/wp-embed.min.js?ver=5.0.2 HTTP/1.1" 200 753
172.17.0.1 - - [27/Dec/2018:04:31:01 +0000] "GET /wp-includes/css/dist/block-library/theme.min.css?ver=5.0.2 HTTP/1.1" 200 452
172.17.0.1 - - [27/Dec/2018:04:31:01 +0000] "GET /wp-includes/css/dist/block-library/style.min.css?ver=5.0.2 HTTP/1.1" 200 4281
172.17.0.1 - - [27/Dec/2018:04:31:01 +0000] "GET /wp-content/themes/twentynineteen/style.css?ver=1.1 HTTP/1.1" 200 19371
172.17.0.1 - - [27/Dec/2018:04:31:01 +0000] "GET /wp-content/themes/twentynineteen/print.css?ver=1.1 HTTP/1.1" 200 1230

Y eso es todo por ahora. Asegúrate de revisar la documentación oficial de Ksonnet y ks help para saber más sobre lo que Ksonnet puede hacer para ayudarte a desplegar tus aplicaciones en cualquier clúster de Kubernetes.


Errata

Si encuentras algún error o tienes alguna sugerencia, por favor envíame un mensaje para que pueda corregirlo.



No tienes cuenta? Regístrate aqui

Ya registrado? Iniciar sesión a tu cuenta ahora.

Iniciar session con GitHub
Iniciar sesion con Google
  • Comentarios

    Online: 0

Por favor inicie sesión para poder escribir comentarios.

by Gabriel Garrido