Abrir publicación
PowerDNS sobre Docker o Kubernetes, fácil y rápido 1

PowerDNS sobre Docker o Kubernetes, fácil y rápido

Que es PowerDNS y DNS como servicio crítico

PowerDNS es un servidor DNS, siendo este un servicio especialmente crítico en cualquier infraestructura que deseemos desplegar, ya que es el principal punto de conexión entre servicios y operadores.

Si de todas las opciones que nos encontramos cuando buscamos un servidor DNS (podemos ver una larga lista en https://en.wikipedia.org/wiki/Comparison_of_DNS_server_software) buscamos las tres siguientes condiciones: que se pueda gestionar fácilmente, despliegue sencillo y OpenSource nos vamos a quedar en una solo opción: PowerDNS y para su gestión PowerDNS-admin.

PowerDNS ( cuyo desarrollo se puede ver en https://github.com/PowerDNS/pdns y tiene más de 1800 estrellas) es un potente servidor DNS cuya características más interesantes para la gestión son disponer de un servicio web con una potente API y ser capaz de almacenar la información en bases de datos, como MySQL.

Y seleccionamos PowerDNS-Admin por dos razones: Está activamente mantenido (https://github.com/ngoduykhanh/PowerDNS-Admin, más de 750 estrellas) y visualmente es un entorno más amigable al tener un formato parecido al que las herramientas de RedHat están usando actualmente.

¿Porqué PowerDNS con PowerDNS-Admin?

Porque conforman un potente paquete donde tenemos las siguientes ventajas:

  • Fácil de instalar
  • Fácil de mantener
  • Interfaz intuitivo
  • Todo está guardado en una base de datos (lo que facilita replicación, copias de seguridad, alta disponibilidad, etc).
  • No requiere configuración especial del navegador (como es el caso de RedHat IDM que requiere instalar el certificado del servidor).
  • Para la gestión de los operadores dispone de autenticación contra múltiples fuentes (LDAP, AD, SAML, Github, Google, etc).
  • Dispone de control sobre los permisos de acceso a los dominios

A estas ventajas hay que unirles la existencia de múltiples contenedores que facilitan sobremanera como desplegar y actualizar está solución.

PowerDNS sobre Docker o Kubernetes, fácil y rápido 2

Desplegar PowerDNS con Docker-Composer

La solución con PowerDNS consta de tres partes: el servidor dns, para el cual haremos uso de contenedor pschiffe/pdns-mysql:alpine (https://github.com/pschiffe/docker-pdns/tree/master/pdns), el servidor de base de datos mariadb a través del contenedor yobasystems/alpine-mariadb
(https://github.com/yobasystems/alpine-mariadb) y el contenedor aescanero/powerdns-admin que hemos explicado en un post anterior (https://www.disasterproject.com/index.php/2019/08/docker-reducir-tamano/).

Es importante indicar que los tres contenedores tienen un mantenimiento activo y son de reducido tamaño, lo que permite un rápido despliegue. Los puertos 53/UDP y 9191/TCP deben de estar disponibles en la máquina que ejecute los contenedores.

Para poder dotar de espacio de almacenamiento en la base de datos se le ha añadido un volumen a la base de datos mariadb, para obtener de esa manera persistencia en la configuración de los elementos que conforman la solución.

Los parámetros de acceso a la

$ mkdir ~/mysql
$ git clone https://github.com/aescanero/docker-powerdns-admin-alpine
$ cd docker-powerdns-admin-alpine
$ LOCALPATH="~/mysql" DOMAIN="disasterproject.com" DB_USERNAME="powerdns" DB_USER_PASSWORD="password" DB_ROOT_PASSWORD="password" DB_NAME="powerdns" PDNS_API_KEY="random" docker-compose up -d
$ docker-compose ps

Una vez desplegada la solución accedemos a ella a la URL: http://WHERE_IS_RUNNING_DOCKER:9191 donde se nos aparece la pantalla de inicio de sesión que veremos en la última sección.

Para eliminar el despliegue realizado (excepto la base de datos que tenemos en ~/mysql) ejecutamos:

$ cd ~/docker-powerdns-admin-alpine
$ docker-compose stop
$ docker-compose rm

Desplegar PowerDNS con Podman

Una opción incluso más rápida es desplegar el entorno con Podman (del cual hemos hablado en https://www.disasterproject.com/index.php/2019/07/elegir-entre-docker-o-podman-para-entornos-de-pruebas-y-desarrollo/ y que nos permite lanzar los contenedores explicados en el punto anterior con la diferencia de que se ejecutan en linea y no mediante un guión como el caso anterior.

A diferencia del caso anterior nos obliga a gestionar la red interna de los contenedores, en este caso hemos elegido 10.88.0.254 para el servidor de base de datos, 10.88.0.253 para powerdns-admin y 10.88.0.252, los puertos 53/UDP y 9191/TCP deben de estar disponibles en la máquina que ejecute los contenedores.

mkdir ~/mysql
export LOCALPATH=`pwd`"/mysql"
export DOMAIN="disasterproject.com"
export DB_USERNAME="powerdns"
export DB_USER_PASSWORD="password"
export DB_ROOT_PASSWORD="password"
export DB_NAME="powerdns"
export PDNS_API_KEY="random"

sudo podman run -d -v ${LOCALPATH}:/var/lib/mysql \
-e MYSQL_PASSWORD="${DB_USER_PASSWORD}" \
-e MYSQL_DATABASE="${DB_NAME}" \
-e MYSQL_USER="${DB_USERNAME}" \
-e MYSQL_ROOT_PASSWORD="${DB_ROOT_PASSWORD}" \
--ip 10.88.0.254 --name mysql yobasystems/alpine-mariadb

sudo podman run -d \
-e PDNS_api_key="secret" \
-e PDNS_master="yes" \
-e PDNS_api="yes" \
-e PDNS_webserver="yes" \
-e PDNS_webserver_address="0.0.0.0" \
-e PDNS_webserver_allow_from="0.0.0.0/0" \
-e PDNS_webserver_password="secret" \
-e PDNS_version_string="anonymous" \
-e PDNS_default_ttl="1500" \
-e PDNS_soa_minimum_ttl="1200" \
-e PDNS_default_soa_name="ns1.${DOMAIN}" \
-e PDNS_default_soa_mail="hostmaster.${DOMAIN}" \
-e MYSQL_ENV_MYSQL_HOST="10.88.0.254" \
-e MYSQL_ENV_MYSQL_PASSWORD="${DB_USER_PASSWORD}" \
-e MYSQL_ENV_MYSQL_DATABASE="${DB_NAME}" \
-e MYSQL_ENV_MYSQL_USER="${DB_USERNAME}" \
-e MYSQL_ENV_MYSQL_ROOT_PASSWORD="${DB_ROOT_PASSWORD}" \
-p 53:53 --ip 10.88.0.252 --name powerdns pschiffe/pdns-mysql:alpine

sudo podman run -d -e PDNS_PROTO="http" \
-e PDNS_API_KEY="${PDNS_API_KEY}" \
-e PDNS_HOST="10.88.0.252" \
-e PDNS_PORT="8081" \
-e PDNSADMIN_SECRET_KEY="secret" \
-e PDNSADMIN_SQLA_DB_HOST="10.88.0.254" \
-e PDNSADMIN_SQLA_DB_PASSWORD="${DB_USER_PASSWORD}" \
-e PDNSADMIN_SQLA_DB_NAME="${DB_NAME}" \
-e PDNSADMIN_SQLA_DB_USER="${DB_USERNAME}" \
-p 9191:9191 --ip 10.88.0.253 --name powerdns-admin aescanero/powerdns-admin

Una vez desplegada la solución accedemos a ella a la URL: http://WHERE_IS_RUNNING_PODMAN:9191 donde se nos aparece la pantalla de inicio de sesión que veremos en la última sección.

Desplegar PowerDNS en Kubernetes

Para desplegar este paquete de gestión DNS en Kubernetes, que se realizará en el post https://www.disasterproject.com/index.php/2019/09/powerdns-kubernetes-helm/ haremos uso de Helm y al igual que en https://www.disasterproject.com/index.php/2019/07/como-lanzar-un-esquema-chart-de-helm-sin-instalar-helm/ veremos que en Helm v3 no se requiere el uso de Tiller.

Accediendo a PowerDNS-Admin

Una vez abierta la consola accedemos al inicio de sesión, donde nuestro primer paso será crear una cuenta entrando en «Create an account»

PowerDNS sobre Docker o Kubernetes, fácil y rápido 3

Este primer usuario será un administrador de PowerDNS y será el que nos debe permitir crear nuevos usuarios, dominios y los permisos de acceso. Creamos el usuario «admin»:

PowerDNS sobre Docker o Kubernetes, fácil y rápido 4

Una vez creado el registro de «admin» procedemos a iniciar sesión:

PowerDNS sobre Docker o Kubernetes, fácil y rápido 5

Que nos permite acceder a la consola de gestión y en el menú de la izquierda vemos la parte de configuración, en donde seleccionaremos «Authentication»:

PowerDNS sobre Docker o Kubernetes, fácil y rápido 6

En los parámetros de configuración, lo primero que hemos de quitar es «Allow users to sign up» para no permitir nuevos registros desde el área de inicio de sesión y si es necesario configurar cualquier medio de autenticación externo que nos ofrece la plataforma.

PowerDNS sobre Docker o Kubernetes, fácil y rápido 7

El siguiente paso es ir a la sección «New Domain» para la creación de nuevos dominios y posteriormente a la sección «Users» donde crearemos nuevos usuarios.

PowerDNS sobre Docker o Kubernetes, fácil y rápido 8

La gestión de los dominios es realmente fácil e intuitiva y la asignación de usuarios a dominios se realiza fácilmente desde la pantalla principal dashboard).

Kubernetes: aventuras y desventuras de parchear (kubectl patch).

Kubernetes es una potente herramienta de orquestación de contenedores donde se ejecutan muchos y diferentes objetos que en algún momento nos va a interesar modificar.

Para ello Kubernetes nos ofrece un interesante mecanismo: patch que vamos a explicar y veremos que está lejos de ser una herramienta lo bastante potente como sería deseable.

Operaciones de parcheo (sustitución) en Kubernetes

Según la documentación de Kubernetes y las normas de la API de Kubernetes se definen tres tipos (con –type):

Strategic

Es el tipo de parche que usa Kubernetes por defecto y es un tipo nativo, definido en el SIG, sigue la estructura del objeto original pero indicando los cambios (por defecto unir: merge, por eso se conoce por strategic merge patch) en un fichero yaml. Por ejemplo si tenemos el siguiente servicio (en el fichero service.yml):

apiVersion: v1
kind: Service
metadata:
  labels:
    app: traefik
  name: traefik
  namespace: kube-system
spec:
  clusterIP: 10.43.122.171
  externalTrafficPolicy: Cluster
  ports:
  - name: http
    nodePort: 30200
    port: 80
    protocol: TCP
    targetPort: http
  - name: https
    nodePort: 31832
    port: 443
    protocol: TCP
    targetPort: https
  selector:
    app: traefik
  type: LoadBalancer

Vamos a utilizar el comando kubectl patch -f service.yml --type="strategic" -p "$(cat patch.yml)" --dry-run -o yaml que nos va a permitir realizar pruebas sobre los objetos sin el peligro de modificar su contenido en el cluster Kubernetes.

Si queremos que dicho servicio escuche por un puerto adicional utilizaremos la estrategia «merge» y aplicaremos el siguiente parche (patch.yml):

spec:
  ports:
  - name: dashboard
    port: 8080
    protocol: TCP
    targetPort: dashboard

Como vemos, el parche solo sigue el objeto servicio hasta donde queremos realizar el cambio (el array «ports») y al ser un cambio de tipo «strategic merge» se dedicará a añadirlo a la lista como se ve en el volcado del comando:

...
spec:
  clusterIP: 10.43.122.171
  externalTrafficPolicy: Cluster
  ports:
  - name: dashboard
    port: 8080
    protocol: TCP
    targetPort: dashboard
  - name: http
    nodePort: 30200
    port: 80
    protocol: TCP
    targetPort: http
...

Pero si en vez de «merge» utilizamos «replace» lo que hacemos es eliminar todo el contenido del subárbol donde estamos al indicar la etiqueta «$patch: replace» y en su lugar pone directamente el contenido del parche. Por ejemplo para cambiar el contenido del array usamos como «patch.yml»:

spec:
  ports:
  - $patch: replace
  - name: dashboard
    port: 8080
    protocol: TCP
    targetPort: dashboard

En este ejemplo se elimina todo el contenido de «ports:» y en su lugar se deja el objeto definido después de la etiqueta «$patch: replace», aunque el orden no es importante, la etiqueta puede ir detrás y tiene el mismo efecto. El resultado del anterior es:

...
  ports:
  - name: dashboard
    port: 8080
    protocol: TCP
    targetPort: dashboard
  selector:
...

Por último «delete» que se indica con «$patch: delete» elimina el contenido del subárbol, aunque se le añada contenido este no es añadido.

spec:
  $patch: delete

El resultado será el contenido de spec vacío:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: traefik
  name: traefik
  namespace: kube-system
spec: {}

Merge

Este tipo de parche es un cambio radical frente a «strategic» ya que requiere la utilización de JSON Merge Patch (RFC7386), se puede aplicar como yaml o json y se basa en un procedimiento de aplicación de cambios con a partir de una pareja objetivo y contenido.

Si el objetivo existe y el valor no es «null» lo sustituye, si el valor es «null» elimina el objetivo. Si el objetivo no existe lo crea. El resto del contenido se mantiene.

Para ver claramente las diferencias si aplicamos el siguiente parche al fichero service.yml

spec:
  ports:
  - name: dashboard
    port: 8080
    protocol: TCP
    targetPort: dashboard

Con kubectl patch -f service.yml --type="merge" -p "$(cat patch.yml)" --dry-run -o yaml obtendremos el mismo resultado que con el tipo «stategic replace», ya que el objeto apuntado (spec.ports) se sustituye por el valor introducido.

Para eliminar un objetivo (Por ejemplo eliminar el selector) usaremos kubectl patch -f service.yml --type="merge" -p '{"spec": {"selector": null}}' --dry-run -o yaml

Y para modificar el selector también podemos usar un json como kubectl patch -f service.yml --type="merge" -p '{"spec": {"selector": "newtraefik"}}' --dry-run -o yaml

JSON

Los dos tipos de parches/sustituciones anteriores son interesantes para aplicaciones sencillas como modificar un nombre, cambiar o eliminar una etiqueta o añadir un chequeo a un despliegue sin editarlo.

Pero si necesitamos trabajar con listas, encontramos los mismos muy limitados y ademas muchos objetos no están bien preparados para ser capaces de entender dichos parches (por ejemplo el objeto Ingress sufre de dicho problema) y en vez de modificar las listas siempre reemplaza.

Por eso existe este tercer tipo de parche más eficiente que los dos anteriores, ya que dispone de más operaciones («add», «remove», «replace», «move», «copy», o «test»), y de un apuntador más preciso al objeto que queremos modificar (definido con JavaScript Object Notation (JSON) Pointer RFC6901).

  • «JSON Pointer»

Es la cadena de objetos separados por «/» apuntando al objeto que queremos manipular. Los caracteres «/» y «~» se deben codificar como «~0» y «~1».

Es especialmente interesante para los arrays, ya que puede hacer referencia a un elemento de un array indicando su posición con un número o indicar un elemento no existente con «-«. A diferencia de JSONPath no es capaz de seleccionar un elemento de un Array usando una búsqueda, por lo que aunque este método es una mejor sigue teniendo importantes limitaciones.

El parche tendrá el formato: ‘{«op»:»OPERACIÓN»,»PARÁMETRO1″,»VALOR1″,…}, habitualmente tendrá al menos dos componentes: operación «op» y ruta «path», las operaciones con este tipo de parche se exponen a continuación:

  • Añadir
    • Parámetro operación: «add»
    • Parámetro ruta: «path»
    • Parámetro valor: «value»

Si el objeto apuntado por ruta existe lo reemplaza, si no existe lo crea y si es un Array lo crea en la posición indicada del Array, por lo que para añadir elementos a un Array se usa en el apuntador el elemento «-«.

  • Eliminar
    • Parámetro operación: «remove»
    • Parámetro ruta: «path»

Si el objeto apuntado por la ruta existe lo elimina, junto con la rama correspondiente.

  • Reemplazar
    • Parámetro operación: «replace»
    • Parámetro ruta: «path»
    • Parámetro valor: «value»

Si el objeto apuntado por al ruta existe lo sustituye por el indicado en valor.

  • Mover
    • Parámetro operación: «move»
    • Parámetro ruta origen: «from»
    • Parámetro ruta destino: «path»

Si ambas rutas existen, procederá a copiar el objeto apuntado por la ruta origen a la ruta destino y eliminará el que exista en la ruta origen.

  • Copiar
    • Parámetro operación: «copy»
    • Parámetro ruta origen: «from»
    • Parámetro ruta destino: «path»

Si ambas rutas existen, procederá a copiar el objeto apuntado por la ruta origen a la ruta destino.

  • Comprobar
    • Parámetro operación: «test»
    • Parámetro ruta: «path»
    • Parámetro valor: «value»

Compara el objeto pasado como valor con el que existe en la ruta, si la ruta no existe o el objeto es diferente devolverá un error.

Aplicando un parche JSON a un objeto Ingress de Kubernetes

Para entenderlo mejor iremos tomando como ejemplo un elemento Ingress cuya función principal es funcionar como proxy inverso desde el exterior hacia los servicios que corren en la plataforma.

Kubernetes: aventuras y desventuras de parchear (kubectl patch). 9
Los servicios web siempre interesa publicarlos al exterior a través de un Ingress

En el gráfico vemos un Ingress arriba del todo, funcionando como un proxy inverso necesita básicamente conocer tres parámetros: el dominio por el que se publica, la ruta por la que publica y que servicio publicamos.

Kubernetes: aventuras y desventuras de parchear (kubectl patch). 10

Este Ingress tendrá un yaml como el siguiente:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: traefik
  namespace: external-dns
spec:
  rules:
  - host: powerdns-admin.disasterproject.com
    http:
      paths:
      - backend:
          serviceName: powerdns-admin
          servicePort: 80
        path: /

Como podemos ver el formato de especificaciones de un Ingress incluye al menos dos listas, una de servidores y otra de rutas de servicio. Vamos a ver que este tipo de objetos incluso con el parche tipo «json» son de una manipulación compleja.

Podemos usar «add» para crear nuevos «host», por ejemplo un nuevo servicio para la powerdns-api:

kubectl -n external-dns patch ingress traefik --type "json" -p '[{"op":"add","path":"/spec/rules/-","value":{"host":"powerdns-api.disasterproject.com","http":{"paths":[{"backend":{"serviceName":"powerdns-api","servicePort":8081 },"path":"/"}]} } }]'

Como podemos ver, añadimos un nuevo host al final de las reglas con el resultado:

Kubernetes: aventuras y desventuras de parchear (kubectl patch). 11

Ahora tenemos dos elementos en el Array de reglas y dentro un Array de backends y tenemos un importante problema porque JSON Pointer no es capaz de buscar y solo se le puede indicar un objeto en un Array por su orden (0,1,2,3,etc). La única solución actualmente es hacer una búsqueda con jsonpath y numerar los resultados, por ejemplo con:

$ kubectl -n external-dns get ingress/traefik -o jsonpath='{.spec.rules[*].host}'|tr ' ' '\n'|cat --number
     1  powerdns-admin.disasterproject.com
     2  powerdns-api.disasterproject.com

Si queremos modificar la ruta de powerdns-api «/» a «/api» ya tenemos el número de orden para el JSONPointer (el número de orden en el array empieza por 0, por lo que debemos restar uno):

$ kubectl -n external-dns patch ingress/traefik --type "json" -p '[{"op":"replace","path":"/spec/rules/1/http/paths/0/path","value":"/api" }]'

$ kubectl -n external-dns get ingress/traefik -o yaml
...
  - host: powerdns-api.disasterproject.com
    http:
      paths:
      - backend:
          serviceName: powerdns-api
          servicePort: 8081
        path: /api
...

¿Pero y si solo quiero modificar el dominio donde está sirviendo la ruta «/api»? JSON Pointer muestra importantes limitaciones en este punto, ya que tenemos que ir mirando (seguramente con scripts) por cada array el orden que ocupa en el mismo. Claramente le falta la potencia de jsonpath que permite hacer consultas como la siguiente que nos va a devolver el backend del dominio powedns-api.disasterproject.com que tenga un path «/api»:

$ kubectl get -n external-dns ingress traefik -o jsonpath='{.spec.rules[?(@.host=="powerdns-api.disasterproject.com")].http.paths[?(@.path=="/api")]}'

map[backend:map[serviceName:powerdns-api servicePort:8081] path:/api]

En este jsonpath vemos dos buquedas @.host==»powerdns-api.disasterproject.com» y @.path==»/api», no disponibles en ninguna de las herramienta de parcheo. Lo mas avanzado que dispone JSON patch es la operación test, pero adolece de los problemas de JSON Pointer:

$ kubectl -n external-dns patch ingress traefik --type="json" -p '[{"op": "test","path": "/metadata/name","value": "traefik2"}]' --dry-run -o yaml
error: Testing value /metadata/name failed

Y por lo tanto es ineficiente en scripts, dejando como única solución jsonpath para listar todos los objetos, filtrar los resultados, coger el indice obtenido e ir realizando de nuevo la operación para todos los arrays que posea el objeto que vamos a parchear.

Referencias

Todas las pruebas se han realizado en el entorno de pruebas definido en https://www.disasterproject.com/index.php/2019/07/12/entorno-minimo-para-demos-de-kubernetes/ y en https://www.disasterproject.com/index.php/2019/07/18/kubernetes-mas-simple-k3s/

Abrir publicación
Kubernetes más simple: K3s 12

Kubernetes más simple: K3s

K3s ( https://k3s.io/ ) es una solución Kubernetes creada por Rancher Labs ( https://rancher.com/ ) que nos promete fácil instalación, pocos requisitos y un uso de memoría mínimo.

Para el planteamiento de un entorno Demo/Desarrollo esto se convierte en una gran mejora sobre lo que hemos hablado anteriormente en https://www.disasterproject.com/index.php/2019/07/12/entorno-minimo-para-demos-de-kubernetes/, donde la creación del entorno es compleja y requiere de muchos recursos aunque sea Ansible quien realice el trabajo difícil.

Pasamos a comprobar si es cierto lo que se nos presenta y si podemos incluir las herramientas metallb (https://metallb.universe.tf/) que nos permitirá emular la potencia de los balanceadores de los entornos Cloud y K8dash (https://github.com/herbrandson/k8dash) que nos permitirá un seguimiento de estado de la infraestructura .

La configuración de las máquinas virtuales la realizamos de la misma manera que para Kubernetes, con la instalación de dependencias:

#Debian
sudo apt-get install -y ebtables ethtool socat libseccomp2 conntrack ipvsadm
#Centos
sudo yum install -y ebtables ethtool socat libseccomp conntrack-tools ipvsadm

Descargamos la última versión de k3s desde https://github.com/rancher/k3s/releases/latest/download/k3s y la ponemos en /usr/bin con permisos de ejecución en todos los nodos.

K3s incluye tres servicios «extras» que nos van a cambiar el planteamiento inicial que usamos para Kubernetes, el primero es Flannel ( https://github.com/coreos/flannel) que está integrado en K3s y nos va a realizar toda la capa de gestión de red interna a Kubernetes, aunque no es tan completo en características como Weave (por ejemplo soporte multicast) cumple con ser compatible con Metallb. Una comparación muy completa de proveedores de red de Kubernetes se puede ver en https://rancher.com/blog/2019/2019-03-21-comparing-kubernetes-cni-providers-flannel-calico-canal-and-weave/ .

El segundo servicio es Traefik ( https://traefik.io/ ) que realiza funciones de entrada desde fuera del entorno Kubernetes, es un potente proxy inverso/balanceador con múltiples características que realizará las funciones de red de capa 7 por detrás de Metallb que realizará las funciones de balanceador de capa de red 3.

El último servicio «extra» de K3s es servicelb, que permite dispone de balaceador de carga de las aplicaciones, el problema de este servicio es que trabaja en capa 3 y sobre los mismos puntos que metallb, por lo que no podemos instalarlo.

En el primer nodo (que será el master) a instalar ejecutamos /usr/bin/k3s server --no-deploy servicelb --bind-address IP_MÁQUINA, si queremos que se realize la ejecución cada vez que se inicie la máquina configuramos un servicio creando el archivo /etc/systemd/system/k3smaster.service

[Unit]
Description=k3s

[Service]
ExecStart=/usr/bin/k3s server --no-deploy servicelb --bind-address 192.168.8.10
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

Y ejecutamos

sudo systemctl enable k3smaster
sudo systemctl start k3smaster

Para que se lance K3s e instale el nodo master, al final de la instalación (unos 20 segundos), guardaremos el contenido del fichero /var/lib/rancher/k3s/server/node-token ya que es el código de activación para los nodos (token), es importante ver que está en base64 y para usarlo hay que decodificarlo.

Antes de preparar los nodos procedemos a instalar Metallb y el panel K8dash:

$ sudo mkdir ~/.kube
$ sudo cp -i /etc/rancher/k3s/k3s.yaml $HOME/.kube/config
$ sudo k3s kubectl apply -f "https://raw.githubusercontent.com/danderson/metallb/master/manifests/metallb.yaml"
$ sudo k3s kubectl apply -f "https://raw.githubusercontent.com/herbrandson/k8dash/master/kubernetes-k8dash.yaml"

Debemos observar que para reducir al máximo el espacio, kubectl está incluido dentro del propio ejecutable k3s, lo que lo hace ideal para entornos con muy poco espacio de almacenamiento.

Para activar Metallb le creamos una configuración que tendrá la forma:

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: my-ip-space
      protocol: layer2
      addresses:
      - 192.168.8.240/28

Que al aplicar con k3s kubectl apply -f pool.yml configurará Metallb para que en el caso de que existan servicios con loadBalancer utilicen una de las IPs definidas en el rango especificado, en nuestro ejemplo corresponde a la red definida en inventory.k3s.yml y el rango x.x.x.240 a x.x.x.254.

apiVersion: v1
kind: Service
metadata:
name: k8dashlb
namespace: kube-system
spec:
ports:

  • name: http
    port: 80
    protocol: TCP
    targetPort: 4654
    selector:
    k8s-app: k8dash
    type: LoadBalancer

configurará Metallb para que en el caso de que existan servicios con loadBalancer utilicen una de las IPs definidas en el rango especificado, en nuestro ejemplo corresponde a la red definida en inventory.k3s.yml y el rango x.x.x.240 a x.x.x.254.

Para probar dicho cargamos un servicio para acceder a K8dash:

apiVersion: v1
kind: Service
metadata:
  name: k8dashlb
  namespace: kube-system
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 4654
  selector:
    k8s-app: k8dash
  type: LoadBalancer

Aplicamos k3s kubectl apply -f service.yml y vamos a ver que IPs tienen los servicios activos:

$sudo k3s kubectl get services -A
NAMESPACE     NAME         TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
default       kubernetes   ClusterIP      10.43.0.1      <none>          443/TCP                      32m
kube-system   k8dash       ClusterIP      10.43.183.3    <none>          80/TCP                       31m
kube-system   k8dashlb     LoadBalancer   10.43.86.34    192.168.8.240   80:32324/TCP                 31m
kube-system   kube-dns     ClusterIP      10.43.0.10     <none>          53/UDP,53/TCP,9153/TCP       32m
kube-system   traefik      LoadBalancer   10.43.91.178   192.168.8.241   80:30668/TCP,443:31859/TCP   30m

Vemos por un lado que los servicios marcados como loadBalancer han cogido correctamente las IPs, tanto el que acabamos de configurar, como el servicio Traefik desplegado por K3s.

En el resto de nodos ejecutamos /usr/bin/k3s agent --server https://IP_MASTER:6443 --token TOKEN_DECODIFICADO_BASE64, si queremos que la ejecución sea cada vez que se inicie la máquina configuramos un servicio creando el archivo /etc/systemd/system/k3s.service

[Unit]
Description=k3s

[Service]
ExecStart=/usr/bin/k3s agent --server https://IP_MASTER:6443 --token TOKEN_DECODIFICADO_BASE64
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

Y ejecutamos para configurar el servicio

sudo systemctl enable k3s
sudo systemctl start k3s
Si accedemos a la IP de servicio para K8dash obtenida anteriormente y con el token que no da el comando k3s kubectl get secret `k3s kubectl get secret|grep ^k8dash|awk '{print $1}'` -o jsonpath="{.data.token}"|base64 -d (nota: el Ansible al finalizar de ejecutar nos devolverá la IP y el token para acceder) podemos comprobar el funcionamiento de la plataforma:
Kubernetes más simple: K3s 13

Por último una pequeña comparación entre kubernetes y k3s en uso de memoria, ambos recién instalados:

  • Kubernetes master: 574 MB
  • Kubernetes nodo: 262 MB
  • K3s master: 260 MB
  • K3s node: 137 MB
Abrir publicación
Kubernetes: Crear un entorno mínimo para demos 14

Kubernetes: Crear un entorno mínimo para demos

Cada día más los entornos plantean una migración/utilización Cloud o entornos Kubernetes/Openshift y se necesita cumplir dichos requisitos de los clientes para la realización de demostraciones para clientes.

Kubernetes no es un entorno amigable para llevarlo en un portatil de media capacidad (8GB a 16 GB de RAM) y menos con una demo que requiera ciertos recursos.

Desplegar Kubernetes basandonos en kubeadm, containerd, metallb y weave

Este caso se basa en el despliegue basado en Kubeadm (https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/) para el despliegue de Kubernetes, usando containerd (https://containerd.io/) como gestor del ciclo de vida de los contenedores y para poder disponer de una gestión de red mínima usaremos metallb (https://metallb.universe.tf/) que nos permitirá emular la potencia de los balanceadores de los entornos Cloud y Weave (https://www.weave.works/blog/weave-net-kubernetes-integration/) que nos permite la gestión de red de los contenedores y se integra sin problemas con metallb.

Por último y aprovechando la infraestructura desplegamos el gestor en tiempo real de los recursos K8dash (https://github.com/herbrandson/k8dash) que nos permitirá un seguimiento de estado de la infraestructura y de las aplicaciones que desplegamos en la misma.

Aunque los roles de Ansible que hemos utilizado con anterioridad (ver https://github.com/aescanero/disasterproject) nos permiten desplegar el entorno con facilidad y limpieza vamos a examinar el mismo para entender como los cambios que usaremos en siguientes capítulos (usar k3s y microk8s) repercuten de manera importante en la disponibilidad y rendimiento del entorno de demo/desarrollo.

Primer paso: Instalar Containerd

El primer paso en la instalación son las dependencias que tiene Kubernetes y una muy buena referencia sobre las mismas es la documentación que Kelsey Hightower pone a disposición de aquellos que necesitan conocer a fondo Kubernetes (https://github.com/kelseyhightower/kubernetes-the-hard-way), especialmente de todos aquellos que están interesados en certificaciones Kubernetes como CKA (https://www.cncf.io/certification/cka/).

Kubernetes: Crear un entorno mínimo para demos 15

Empezamos por una serie de paquetes de red

#Debian
sudo apt-get install -y ebtables ethtool socat libseccomp2 conntrack ipvsadm
#Centos
sudo yum install -y ebtables ethtool socat libseccomp conntrack-tools ipvsadm

Instalamos el gestor de vida de los contenedores (Containerd que incluye CRI y CNI) y aprovechamos el paquetes que viene con los interfaces de red de Kubernetes (CNI o Container Network Interface)

sudo sh -c "curl -LSs https://storage.googleapis.com/cri-containerd-release/cri-containerd-cni-1.2.7.linux-amd64.tar.gz |tar --no-overwrite-dir -C / -xz"

El paquete incluye el servicio para systemd por lo que basta con iniciar el servicio:

sudo systemctl enable containerd
sudo systemctl start containerd

Segundo Paso: kubeadm y kubelet

Ahora descargamos los ejecutables de kubernetes, en el caso de la primera máquina a configurar será el master y tenemos que descargar los binarios kubeadm (el instalador de kubernetes), kubelet (el agente que se conectará con containerd en cada máquina, para saber cual es la versión estable de kubernetes ejecutamos:

VERSION=`curl -sSL https://dl.k8s.io/release/stable.txt`

Y descargamos los binarios (en el master todos y en los nodos solo es necesario kubelet)

sudo curl -Ol https://storage.googleapis.com/kubernetes-release/release/$VERSION/bin/linux/amd64/{"kubectl","kubelet","kubeadm"} -o /usr/bin/"#1"
sudo chmod u+x /usr/bin/{"kubectl","kubelet","kubeadm"}

Configuramos un servicio para kubelet en cada máquina creando el archivo /etc/systemd/system/kubelet.service que dependerá de si la máquina tiene la función de master o nodo. Para el master tenemos:

[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=http://kubernetes.io/docs/

[Service]
# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
Environment="KUBELET_EXTRA_ARGS='--network-plugin=cni --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --node-ip={{ vm_ip }}'"
Environment="KUBELET_KUBECONFIG_ARGS='--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf'"
Environment="KUBELET_CONFIG_ARGS='--config=/var/lib/kubelet/config.yaml'"
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

Para el resto de nodos:

[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=http://kubernetes.io/docs/

[Service]
Environment="KUBELET_EXTRA_ARGS='--network-plugin=cni --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --node-ip={{ vm_ip }}'"
Environment="KUBELET_KUBECONFIG_ARGS='--bootstrap-kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig --kubeconfig=/var/lib/kubelet/kubeconfig'"
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_EXTRA_ARGS
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

Una vez cargada la configuración procedemos a utilizar kubeadm en el primer node (master), indicando la ip que se va a publicar, la red interna de los pods (un /16, aunque no olvidemos que es un entorno de demo/desarrollo por lo que no debe ser un problema). No vamos a utilizar kubeadm en el resto de nodos, así que no necesitamos recoger información de la ejecución de este comando.

$ sudo kubeadm init --apiserver-advertise-address {{ vm_ip }} --pod-network-cidr=10.244.0.0/16 --cri-socket /run/containerd/containerd.sock
Kubernetes: Crear un entorno mínimo para demos 16

Tercer paso: MetalLB (balanceador carga capa 4) y Weave (gestión de red)

Antes de preparar los nodos procedemos a cargar dos elementos, el primero «metallb» nos permitirá tener servicios «balanceables por carga: loadbalancer» accesibles en el mismo rango que las máquinas virtuales y el segundo «weave» es un gestor de red que permitirá la comunicación entre pods que se ejecutan entre máquinas diferentes dentro de la red que hemos definido anteriormente. Ambos servicios se cargan con los siguientes comandos ejecutados en master:

$ sudo mkdir ~/.kube
$ sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config
$ sudo kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(sudo kubectl version | base64 | tr -d '\n')"
$ sudo kubectl apply -f "https://raw.githubusercontent.com/danderson/metallb/master/manifests/metallb.yaml"

Cuarto paso: Añadir nodos

Para añadir los nodos hemos de crear un usuario, como no disponemos de integración con ldap o directorio activo, usaremos las cuentas de sistema o sa, generamos la cuenta initnode para añadir los nodos con el siguiente yaml:

apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: null
  name: initnode
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  creationTimestamp: null
  name: initauth
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:node-bootstrapper
subjects:
- kind: ServiceAccount
  name: initnode
  namespace: kube-system

Una vez generada la cuenta de servicio ejecutamos kubectl get serviceaccount -n kube-system initnode -o=jsonpath="{.secrets[0].name}"
que nos dará el nombre del token y kubectl get secrets "NOMBRE_TOKEN" -n kube-system -o=jsonpath="{.data.token}"|base64 -d nos dará el valor del mismo que hemos de guardar.

Además de crear el usuario hay que crear una configuración de inicio para los nodos, para ello ejecutamos los siguientes comandos:

$ sudo kubectl config set-cluster kubernetes --kubeconfig=~/bootstrap-kubeconfig --certificate-authority=/etc/kubernetes/pki/ca.crt --embed-certs --server
$ sudo kubectl config set-credentials initnode --kubeconfig=~/bootstrap-kubeconfig --token=VALOR_TOKEN
$ sudo kubectl config set-context initnode@kubernetes --cluster=kubernetes --user=initnode --kubeconfig=~/bootstrap-kubeconfig
$ sudo kubectl config use-context initnode@kubernetes --kubeconfig=~/bootstrap-kubeconfig

El archivo generado hemos de copiarlo en todos los nodos en /var/lib/kubelet/bootstrap-kubeconfig y procedemos a iniciar el servicio kubelet en cada nodo que se encargará de contactar con el master y pedir acceso.

sudo systemctl enable kubelet
sudo systemctl start kubelet

Para autorizar el o los nodos en el master hemos de ir al mismo y consultar por las peticiones con el comando kubectl get certificatesigningrequests.certificates.k8s.io
, y autorizarlas con el comando kubectl certificate approve

Una vez autorizados los nodos dispondremos de capacidad de utilizar está plataforma.

Elegir entre Docker o Podman para entornos de pruebas y desarrollo

Muchas veces nos encontramos que tenemos muy pocos recursos y necesitamos un entorno para realizar una completa demostración de producto a un cliente.

En esos casos vamos a necesitar simular un entorno de la manera más sencilla y con menos recursos posibles y para ello usaremos contenedores, pero ¿cual es la mejor solución para esos pequeños entornos?

Docker es el entorno de contedores estandar en el mercado, es el más extendido y conforma un conjunto de herramientas extraordinariamente potentes como son un cliente desde linea de comandos, un API server, un gestor del ciclo de vida de los contenedores (containerd), un lanzador de contenedores (runc).

Elegir entre Docker o Podman para entornos de pruebas y desarrollo 17

Podman es un entorno de contenedores que no utiliza un servicio y por lo tanto no dispone de un API server, las peticiones se hacen únicamente desde la linea de comandos, lo que tiene ventajas y desventajas que explicaremos al final.

Instalar docker es realmente fácil, ya que docker suministra un script que realiza el proceso de preparar y configurar los requisitos y los repositorios necesario y por último instala y configura docker dejando el servicio listo para usar.

Instalar podman es fácil en un entorno Centos (yum install -y podman para Centos 7 y yum install -y container-tools para Centos 8) pero necesita cierto trabajo en un entorno Debian:

# sudo apt update && sudo apt install -y software-properties-common dirmngr
# sudo apt-key adv --keyserver ha.pool.sks-keyservers.net --recv-keys 0x018BA5AD9DF57A4448F0E6CF8BECF1637AD8C79D
# sudo sh -c "echo 'deb http://ppa.launchpad.net/projectatomic/ppa/ubuntu bionic main' > /etc/apt/sources.list.d/container.list"
# sudo apt update && sudo apt install -y podman skopeo buildah uidmap debootstrap

En nuestro caso hemos utilizado los roles Ansible desarrollados en https://github.com/aescanero/disasterproject, para desplegar dos máquinas virtuales, una con podman y la otra con docker.

En el caso de usar una distribución basada en Debian instalamos Ansible:

$ sudo sh -c 'echo "deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main" >>/etc/apt/sources.list'
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 93C4A3FD7BB9C367
$ sudo apt-get update && sudo apt-get install -y ansible 

Procedemos a descargar el entorno y configurar Ansible:

$ git clone https://github.com/aescanero/disasterproject
$ cd disasterproject/ansible
$ chmod 600 files/insecure_private_key

Editamos el archivo inventory.yml y le ponemos el siguiente formato:

all:
  children:
    vms:
      hosts:
        MACHINE_NAME1:
          memory: MEMORY_IN_MB
          vcpus: vCPUS_FOR_VM
          vm_ip: "IP_VM_MACHINA_NAME1"
          linux_flavor: "debian|centos"
          container_engine: "docker|podman"
        MACHINE_NAME2:
          memory: MEMORY_IN_MB
          vcpus: vCPUS_FOR_VM
          vm_ip: "IP_VM_MACHINA_NAME2"
          linux_flavor: "debian|centos"
          container_engine: "docker|podman"
      vars:
        network_name: NETWORK_NAME
        network: "VM_NETWORK"

Existen algunas variables globales que cuelgan de «vars:», que son:

  • network_name: Nombre descriptivo de la red de libvirt que crearemos y que además será el nombre del interface que se configurará en el anfitrión KVM y que servirá de puerta de enlace de las máquinas virtuales
  • network: los tres primeros campos de la dirección IPv4 para confirmar una red con máscara 255.255.255.0, las máquinas virtuales deberán tener una IP de dicho rango (menos 0,1 y 255)

El formato de cada equipo cuelga se define por los siguientes atributos:

  • nombre: Nombre descriptivo de la máquina virtual a desplegar, será además el hostname del equipo
  • memory: Memoria de la máquina virtual en MB
  • vcpus: Número de CPUs virtuales en la máquina virtual
  • vm_ip: IP de la máquina virtual, debe pertenecer al rango definido en la variable general «network»
  • linux_flavor: Es la distribución de la máquina virtual, se permiten dos opciones: debian y centos
  • container_engine: (Opcional) Es el motor de contenedores que se puede desplegar en la máquina virtual, se permiten dos opciones: docker y podman

Desplegamos el entorno con:

$ ansible-playbook -i inventory.yml create.yml --ask-become-pass

Como resultado obtenemos dos máquinas virtuales a las cuales podemos acceder vía ssh con ssh -i files/insecure_private_key vagrant@IP_MÁQUINA_VIRTUAL, en cada una de ellas procedemos a lanzar un contenedor, docker run -td nginx y podman run -dt nginx. Una vez desplegados los contenedor nginx, analizamos que servicios se ven ejecutados en cada caso.

  • Docker, arranca 32 procesos para lanzar nginx:
containerd-+-containerd-shim-+-nginx---nginx
                     |                 `-10*[{containerd-shim}]
                     `-8*[{containerd}]
dockerd---9*[{dockerd}]
3194 root      20   0  546332  92220  39252 S  0.0  9.5   0:07.23 dockerd
 2314 root      20   0  463200  41696  25768 S  0.0  4.3   0:00.57 containerd
 3621 root      20   0   32648   5244   4564 S  0.0  0.5   0:00.08 nginx
 3603 root      20   0   11720   4612   3856 S  0.0  0.5   0:00.03 containerd-shim
 3500 root      20   0   20472   2692   1656 S  0.0  0.3   0:00.00 dhclient
 3479 root      20   0   20352   2676   1636 S  0.0  0.3   0:00.00 dhclient
 3656 systemd+  20   0   33100   2508   1468 S  0.0  0.3   0:00.00 nginx
  • Podman, arranca un proceso para lanzar nginx:
conmon-+-nginx---nginx
                 `-{gmain}
 3471 root      20   0   32648   5180   4500 S  0.0  0.5   0:00.06 nginx
 3482 systemd+  20   0   33100   2420   1380 S  0.0  0.2   0:00.00 nginx
 3461 root      20   0   85800   1924   1768 S  0.0  0.2   0:00.00 conmon

Entre las dos máquinas virtuales hay solo 100 MB de diferencia entre ambas:

1,5G docker.qcow2
1,4G podman.qcow2

Como conclusión tenemos los siguientes puntos:

DockerPodman
Gestión de ciclo de vida, por ejemplo reinicio de contenedores que fallan automáticamente, iniciar contenedores automáticamente cuando el equipo se reinicia, ejecuta chequeos sobre los contenedores, iniciar contenedores en un orden determinado, etc.Es compatible con Docker a nivel de CLI, imagen y carga desde registros.
Depende de systemd para gestionar el ciclo de vida de los contenedores.
Requiere más recursos, pero en un entorno de demo/desarrollo con pocos contenedores no debería de requerir importantes recursos.Dispone de más recursos disponibles para los contenedores, lo que es ideal para un entorno de demos en máquinas virtuales.
Ocupa un espacio menor y requiere de menos dependencias.

Podman es un proyecto respaldado por RedHat que está tomando fuerza y muchos servicios de desarrollo se plantean su adopción (por ejemplo el proyecto syslog-ng: https://www.syslog-ng.com/community/b/blog/posts/replacing-docker-with-podman-in-the-syslog-ng-build-container

Más información en como adaptar Podman en Replacing Docker with Podman.

Máquina virtual linux con KVM desde linea de comandos

Si queremos levantar máquinas virtuales en un entorno Linux KVM que no disponga de entorno gráfico podemos levantar máquinas virtuales desde linea de comando usando una plantilla XML.

Este artículo explica como funciona internamente el despliegue que se realiza con Ansible-libvirt en https://www.disasterproject.com/index.php/2019/06/entorno-minimo-kvm-y-ansible/

Instalar Qemu-KVM y Libvirt

Máquina virtual linux con KVM desde linea de comandos 18

Primero debemos tener instalado libvirt y Qemu-KVM que en Ubuntu/Debian se instala con:

$ sudo apt-get install -y libvirt-daemon-system python-libvirt python-lxml

Y en CentOS/Redhat con:

$ sudo yum install -y libvirt-daemon-kvm python-lxml

Para iniciar el servicio haremos $ sudo systemctl enable libvirtd && sudo systemctl start libvirtd

Configurar una plantilla de red

Libvirt nos provee de una poderosa herramienta para gestión de las máquinas virtuales llamada ‘virsh’, la cual debemos de utilizar para poder gestionar las máquinas virtuales KVM desde la linea de comandos.

Para una máquina virtual necesitamos principalmente tres elementos, el primero es una configuración de red que entre otras cosas provea a las máquinas virtuales de IP vía DHCP. Para ello libvirt necesita de una plantilla XML como la siguiente (que llamaremos net.xml):

<network>
  <name>NOMBRE_DE_RED</name>
  <forward mode='nat'>
    <nat>
      <port start='1' end='65535'/>
    </nat>
  </forward>
  <bridge name='NOMBRE_DEL_BRIDGE' stp='on' delay='0'/>
  <ip address='IP_HOST' netmask='MASCARA_RED'>
    <dhcp>
      <range start='INICIO_RANGO_DHCP' end='FIN_RANGO_DHCP'/>
    </dhcp>
  </ip>
</network>

Cuyos elementos principales son:

  • NOMBRE_DE RED: Nombre descriptivo que vamos a dar a la red, por ejemplo red_pruebas o red_producción.
  • NOMBRE_DEL_BRIDGE: Cada red crea una interfaz en el servidor anfitrión que servirá para dar salida y entrada a los paquetes de dicha red hacia el exterior. Aquí le asignamos un nombre descriptivo que nos permita identificar el interfaz.
  • IP_HOST: La IP que dicho interfaz tendrá en el servidor anfitrión y que será la puerta de enlace de las máquinas virtuales
  • MASCARA_RED: Depende de la red, habitualmente para pruebas y demos siempre una clase C (255.255.255.0)
  • INICIO_RANGO_DHCP: Para asignar IPs a las máquinas virtuales libvirt usa un servidor DHCP interno (basado en dnsmasq), aquí definimos la primera IP que podemos servir a las máquinas virtuales.
  • FIN_RANGO_DHCP: Y aquí le decimos la última IP que podemos servir a las máquinas virtuales para su uso.

Preparando la imagen del sistema operativo

El segundo elemento es la imagen de la máquina virtual, la imagen se puede crear o descargar, siendo recomendable lo segundo para reducir el tiempo de despliegue. Una fuente de imágenes para máquinas virtuales con KVM/libvirt es Vagrant ( https://app.vagrantup.com/boxes/search?provider=libvirt ), para obtener una imagen de la máquina virtual que nos interese de las que existen en dicha página nos las descargaremos de https://app.vagrantup.com/NOMBRE_APP/boxes/ETIQUETA_APP/versions/VERSION_APP/providers/libvirt.box siendo NOMBRE_APP el nombre de la aplicación que queremos utilizar (p.e. debian), ETIQUETA_APP es la distribución de dicha aplicación (p.e. stretch64) y por último VERSION_APP es la versión de la aplicación (p.e. 9.9.0). Aún así existen repositorios de imágenes específicos como por ejemplo los de CentOs que están en http://cloud.centos.org/centos/7/vagrant/x86_64/images .

Una vez obtenido el archivo libvirt.box, se descomprime con tar -zcf libvirt.box lo que nos genera tres archivos, uno de los cuales es la imagen de la máquina virtual (box.img) que renombraremos a NOMBRE_DESCRIPTIVO.qcow2 y copiaremos en la carpeta estándar para imágenes de máquinas virtuales de libvirt (/var/lib/libvirt/images) y le daremos permisos al usuario libvirt para que pueda gestionar dicha imagen (chown libvirt /var/lib/libvirt/images/NOMBRE_DESCRIPTIVO.qcow2 )

Es importante indicar que si queremos acceder a la máquina virtual vamos a necesitar una clave que la descargamos con wget -O insecure_private_key https://raw.githubusercontent.com/hashicorp/vagrant/master/keys/vagrant, que le aplicamos permisos para el acceso solo para el usuario actual ( $ chmod 600 insecure_private_key )

Plantilla para la máquina virtual

El tercer elemento para crear la máquina virtual una vez creada la red es una plantilla de máquina virtual, que para un sistema operativo reciente tendrá la siguiente forma:

<domain type='kvm'>
  <name>NOMBRE_VM</name>
  <memory unit='MB'>MEMORY_VM</memory>
  <vcpu>CPU_NUMBER</vcpu>
  <os>
    <type>hvm</type>
    <bootmenu enable='no'/>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
  </features>
  <clock offset='utc'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>destroy</on_crash>
  <devices>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='RUTA_ALMACEN_IMAGENES/NOMBRE_IMAGEN.qcow2'/>
      <target dev='vda' bus='virtio'/>
    </disk>
    <interface type='network'>
      <source network='NOMBRE_DE_RED'/>
      <model type='virtio'/>
    </interface>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <input type='mouse' bus='ps2'>
      <alias name='input0'/>
    </input>
    <input type='keyboard' bus='ps2'>
      <alias name='input1'/>
    </input>
    <graphics type='spice' port='5900' autoport='yes' listen='127.0.0.1'>
      <listen type='address' address='127.0.0.1'/>
      <image compression='off'/>
    </graphics>
    <video>
      <model type='cirrus' vram='16384' heads='1' primary='yes'/>
      <alias name='video0'/>
    </video>
    <memballoon model='virtio'/>
  </devices>
</domain>

Cuyos elementos son:

  • NOMBRE_VM: El nombre descriptivo que nos permitirá identificar la máquina virtual
  • MEMORY_VM: Cuanta memoria en MB le asignamos a la máquina virtual
  • CPU_NUMBER: Cuantas CPU virtuales va a disponer la máquina virtual
  • RUTA_ALMACEN_IMAGENES: La ruta donde hemos guardado la máquina virtual, la estándar es /var/lib/libvirt/images
  • NOMBRE_IMAGEN: Que nombre descriptivo hemos dado a la imagen en el punto anterior
  • NOMBRE_DE_RED: Que nombre descriptivo le hemos dado a la red que va a gestionar la máquina virtual

Desplegando la máquina virtual

Una vez creadas las plantillas y desplegada la imagen ejecutamos:

# sudo virsh net-create plantilla_red.xml
# sudo virsh create plantilla_máquina_virtual.xml

Por último comprobamos que funciona, primero identificamos si está iniciado con # sudo virsh list, segundo obtenemos la ip en uso con # sudo virsh net-dhcp-leases NOMBRE_DE_RED y por último accedemos a la IP asignada con # ssh -i insecure_private_key vagrant@IP

Navegación de entradas

1 2
Volver arriba