Instalación de Kubernetes en Ubuntu con Ansible

2
12066

En este tutorial comprobaremos como instalar Kubernetes de forma sencilla con Ansible.

Índice de contenidos

1. Introducción

Una de las cosas más importantes a la hora de tener una aplicación en producción es que esta esté disponible siempre para los usuarios. Kubernetes nos ofrece una buena infraestructura para aumentar la disponibilidad de nuestra aplicación.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11.2
  • Virtual Box
  • Vagrant 1.8.1
  • Ansible 1.9.3

3. Preparación y configuración del entorno

En tutoriales anteriores ya se ha explicado como instalar Vagrant y Ansible (ver aquí), así que partimos de que ambas herramientas ya están instaladas.

Recordamos que Vagrant es una herramienta que nos permite gestionar máquinas virtuales (usando en nuestro caso VirtualBox por debajo) y que Ansible nos permite automatizar tareas en host remotos via ssh. Vagrant y ansible están muy bien integrados, de forma que utilizaremos Vagrant para crear el entorno con un sistema operativo Ubuntu en el cual instalaremos y jugaremos con Kubernetes. Para ello nos creamos un directorio dónde se encontrará nuestro Vagrantfile, fichero de configuración de Vagrant. Para que se genere el fichero por defecto ejecutamos el comando vagrant init.

Consola comando vagrant init

Nuestro fichero Vagrantfile debe quedar de la siguiente manera:

Vagrantfile
Vagrant.configure(2) do |config|

  config.vm.box = "ubuntu/trusty64"

  config.vm.network "forwarded_port", guest: 8080, host: 8080
  config.vm.network "forwarded_port", guest: 32066, host: 32066

  config.vm.network "private_network", ip: "192.168.90.20"

  config.vm.provider "virtualbox" do |v|
      v.memory = 4096
      v.cpus = 2
  end

  config.vm.define "prueba" do |prueba|
    prueba.vm.provision "ansible" do |ansible|
      ansible.verbose = 'vvv'
      ansible.playbook = "ansible/infraestructure.yml"
    end
  end
end

Analicemos que realizará este fichero de configuración:

  • config.vm.box = «ubuntu/trusty64» : en este apartado indicamos la box que va a utilizar vagrant para crear la máquina virtual.
  • config.vm.network «forwarded_port», guest: 8080, host: 8080 : aquí redireccionamos el puerto de la máquina anfitrión a la máquina virtual de forma que cuando se llame al puerto 8080 de la máquina real esto será redireccionado al puerto 8080 de la máquina virtual. Esto nos sirve para ver la interfaz gráfica de Kubernetes.
  • config.vm.network «private_network», ip: «192.168.90.20» : en este apartado indicamos la ip que tendrá la máquina virtual.
  • v.memory = 4096 : en este apartado la memoria que va a tenr la máquina virtual.
  • Por último definimos el provisionamiento que Vagrant va a realizar en la máquina una vez esté levantada. Para ello uso el ya mencionado Ansible, pero notad que no le especifico en ningún lugar el inventory. Esto está hecho a propósito ya que Vagrant te lo genera automáticamente si no lo creas tú.

Ahora tenemos que crear el directorio Ansible dónde vamos a tener la siguiente estructura:

  • infraestructure.yml: es el playbook principal que se encarga de instalar kubernetes.
  • roles/docker/tasks/docker_install.yml : aquí se encuentran las tareas propias de la instalación docker.
  • roles/docker/tasks/docker_prerequisites.yml : aquí están las tareas que necesitan ser ejecutadas para que se instale docker.
  • roles/docker/tasks/docker_setup.yml : aquí están tareas propias de la configuración.
  • roles/docker/tasks/main.yml : aquí se encuentran las tareas que se van a ejecutar en el rol de docker.
  • roles/kubernetes/files/config-default.sh : es el fichero de configuración dónde se especifican las propiedades del cluster.
  • roles/kubernetes/meta/main.yml : en este fichero se encuentran las dependencias del rol kubernetes.
  • roles/kubernetes/tasks/main.yml : aquí definimos las tareas que se van a ejecutar en el rol kubernetes.
  • roles/kubernetes/vars/main.yml : por ultimo definimos las variables propias del rol aquí.

3.1 Kubernetes

A continuación veremos el contenido del rol Kubernetes.

roles/kubernetes/files/config-default.sh
#!/bin/bash

# Copyright 2015 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

## Contains configuration values for the Ubuntu cluster

# Define all your cluster nodes, MASTER node comes first"
# And separated with blank space like   
export nodes=${nodes:-"root@127.0.0.1"}

# Define all your nodes role: a(master) or i(minion) or ai(both master and minion), must be the order same
role=${roles:-"ai"}
# If it practically impossible to set an array as an environment variable
# from a script, so assume variable is a string then convert it to an array
export roles=($role)

# Define minion numbers
export NUM_NODES=${NUM_NODES:-1}
# define the IP range used for service cluster IPs.
# according to rfc 1918 ref: https://tools.ietf.org/html/rfc1918 choose a private ip range here.
export SERVICE_CLUSTER_IP_RANGE=${SERVICE_CLUSTER_IP_RANGE:-192.168.3.0/24}  # formerly PORTAL_NET
# define the IP range used for flannel overlay network, should not conflict with above SERVICE_CLUSTER_IP_RANGE

# The Ubuntu scripting supports two ways of networking: Flannel and
# CNI.  To use CNI: (1) put a CNI configuration file, whose basename
# is the configured network type plus ".conf", somewhere on the driver
# machine (the one running `kube-up.sh`) and set CNI_PLUGIN_CONF to a
# pathname of that file, (2) put one or more executable binaries on
# the driver machine and set CNI_PLUGIN_EXES to a space-separated list
# of their pathnames, and (3) set CNI_KUBELET_TRIGGER to identify an
# appropriate service on which to trigger the start and stop of the
# kubelet on non-master machines.  For (1) and (2) the pathnames may
# be relative, in which case they are relative to kubernetes/cluster.
# If either of CNI_PLUGIN_CONF or CNI_PLUGIN_EXES is undefined or has
# a zero length value then Flannel will be used instead of CNI.

export CNI_PLUGIN_CONF CNI_PLUGIN_EXES CNI_KUBELET_TRIGGER
CNI_PLUGIN_CONF=${CNI_PLUGIN_CONF:-""}
CNI_PLUGIN_EXES=${CNI_PLUGIN_EXES:-""}
CNI_KUBELET_TRIGGER=${CNI_KUBELET_TRIGGER:-networking}

# Flannel networking is used if CNI networking is not.  The following
# variable defines the CIDR block from which cluster addresses are
# drawn.
export FLANNEL_NET=${FLANNEL_NET:-172.16.0.0/16}

# Optionally add other contents to the Flannel configuration JSON
# object normally stored in etcd as /coreos.com/network/config.  Use
# JSON syntax suitable for insertion into a JSON object constructor
# after other field name:value pairs.  For example:
# FLANNEL_OTHER_NET_CONFIG=', "SubnetMin": "172.16.10.0", "SubnetMax": "172.16.90.0"'

export FLANNEL_OTHER_NET_CONFIG
FLANNEL_OTHER_NET_CONFIG=''

# Admission Controllers to invoke prior to persisting objects in cluster
export ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,SecurityContextDeny

# Path to the config file or directory of files of kubelet
export KUBELET_CONFIG=${KUBELET_CONFIG:-""}

# A port range to reserve for services with NodePort visibility
SERVICE_NODE_PORT_RANGE=${SERVICE_NODE_PORT_RANGE:-"30000-32767"}

# Optional: Enable node logging.
ENABLE_NODE_LOGGING=false
LOGGING_DESTINATION=${LOGGING_DESTINATION:-elasticsearch}

# Optional: When set to true, Elasticsearch and Kibana will be setup as part of the cluster bring up.
ENABLE_CLUSTER_LOGGING=false
ELASTICSEARCH_LOGGING_REPLICAS=${ELASTICSEARCH_LOGGING_REPLICAS:-1}

# Optional: When set to true, heapster, Influxdb and Grafana will be setup as part of the cluster bring up.
ENABLE_CLUSTER_MONITORING="${KUBE_ENABLE_CLUSTER_MONITORING:-true}"

# Extra options to set on the Docker command line.  This is useful for setting
# --insecure-registry for local registries.
DOCKER_OPTS=${DOCKER_OPTS:-""}

# Extra options to set on the kube-proxy command line.  This is useful
# for selecting the iptables proxy-mode, for example.
KUBE_PROXY_EXTRA_OPTS=${KUBE_PROXY_EXTRA_OPTS:-""}

# Optional: Install cluster DNS.
ENABLE_CLUSTER_DNS="${KUBE_ENABLE_CLUSTER_DNS:-true}"
# DNS_SERVER_IP must be a IP in SERVICE_CLUSTER_IP_RANGE
DNS_SERVER_IP=${DNS_SERVER_IP:-"192.168.3.10"}
DNS_DOMAIN=${DNS_DOMAIN:-"cluster.local"}
DNS_REPLICAS=${DNS_REPLICAS:-1}

# Optional: Install Kubernetes UI
ENABLE_CLUSTER_UI="${KUBE_ENABLE_CLUSTER_UI:-true}"

# Optional: Enable setting flags for kube-apiserver to turn on behavior in active-dev
#RUNTIME_CONFIG=""

# Optional: Add http or https proxy when download easy-rsa.
# Add envitonment variable separated with blank space like "http_proxy=http://10.x.x.x:8080 https_proxy=https://10.x.x.x:8443"
PROXY_SETTING=${PROXY_SETTING:-""}

DEBUG=${DEBUG:-"false"}

El fichero config-default se crea por defecto cuando descargas la versión de Kubernetes (se encuentra dentro del directorio kubernetes/cluster/ubuntu). Este fichero se encarga de establecer la configuración por defecto del cluster, como el número de nodos, sus respectivas ip y el número de minions y de maestros del cluster. Las propiedades que hemos modificado del fichero son:

  • export nodes=${nodes:-«root@127.0.0.1»}: Con esta propiedad le indicamos los nodos que va a tener nuestro cluster. Como este es un ejemplo muy sencillo, tenemos un cluster de un único nodo, pero si tuviéramos más deberían de ir las ip aquí separadas por espacios.
  • role=${roles:-«ai»}: Aquí se separan los nodos por espacio y van en el mismo orden en el que hemos definido las ips en la propiedad anterior. a significa master, mientras que i significa minion de forma que estamos indicando que nuestro único nodo es master y minion a la vez.
  • export NUM_NODES=${NUM_NODES:-1}: con esta propiedad indicamos que el número de minions que tiene nuestro cluster es 1.

En el siguiente fichero están descritas las tareas que se van a realizar en el playbook Kubernetes:

roles/kubernetes/tasks/main.yml
---

# file: roles/kubernetes/tasks/main.yml

- name: Create ssh key for this machine (Necesary to start kubernetes)
  user: name=root generate_ssh_key=yes
  register: user_var

- name: Add key to authorized keys
  shell: "cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys"

- name: Download Kubernetes release bundle
  get_url: >
    url="https://github.com/GoogleCloudPlatform/kubernetes/releases/download/v1.2.0/kubernetes.tar.gz"
    dest="{{kubernetes.temporal_path}}/kubernetes.tar.gz"

- name: Untar the kubernetes bundle
  unarchive: >
    src="{{kubernetes.temporal_path}}/kubernetes.tar.gz"
    dest={{kubernetes.path}} copy=no

- name: Unarchive salbase tar to configure the cluster.
  unarchive: >-
    src={{kubernetes.path}}/kubernetes/server/kubernetes-salt.tar.gz
    dest={{kubernetes.path}}/kubernetes/cluster
    copy=no

- name: move saltbase directory outside the unarchive directory
  shell: mv {{kubernetes.path}}/kubernetes/cluster/kubernetes/saltbase {{kubernetes.path}}/kubernetes/cluster
  register: result
  failed_when: "'Directory not empty' not in result.stderr and 'true' in result.failed"

- name: Delete empty directory
  file: >-
    path={{kubernetes.path}}/kubernetes/cluster/kubernetes
    state=absent

- name: Setup default configuration
  copy: >
    src=config-default.sh
    dest={{kubernetes.path}}/kubernetes/cluster/ubuntu
    mode="0755"
    force=yes

- name: Start cluster
  shell: KUBERNETES_PROVIDER=ubuntu ./kube-up.sh chdir={{kubernetes.path}}/kubernetes/cluster
  register: result
  failed_when: "'Text file busy' not in result.stdout and 'true' in result.failed"

Los pasos que sigue este playbook son:

  • Las dos primeras tareas son necesarias para arrancar el servicio de Kubernetes. Este servicio necesita de acceso por ssh, y como lo estamos automatizando con Ansible, no queremos que se detenga la ejecución. Para ello creamos una par clave pública/privada que identifica a la máquina, y añadimos la clave pública al fichero authorized_keys.
  • Nos descargamos la última release de Kubernetes y la descomprimimos en la variable {{kubernetes.path}}, que se encuentra en el fichero roles/kubernetes/defaults/main.yml.
  • Una vez hecho eso, para el arranque del cluster necesitamos un certificado para que el master del cluster pueda actuar como un servidor https. Para ello generamos un certificado general y lo usamos para firmar los certifcicados de la máquina con el script make-ca-cert.sh que se encuentra en el directorio saltabse, que está comprimido por defecto. Lo descomprimimos y lo movemos al directorio correspondiente.
  • Ya estaríamos listos para levantar el cluster, pero necesitamos la configuración que tenemos en el fichero config-default.sh dónde indicamos que solo se nos cree un nodo en la máquina virtual que sea master y minion a la vez.
  • Dejando lo mejor para el final, ejecutamos el comando para levantar el cluster. Comprobamos como antes del comando tenemos que indicarle previamente el provider a utilizar, ubuntu en nuestro caso. En esta tarea usamos la gestión de errores que nos ofrece Ansible. Esta característica está explicada en este tutorial.

Ahora vemos el fichero defaults/main.yml que contiene las variables por defecto que se van a utilizar en el rol:

roles/kubernetes/defaults/main.yml
kubernetes:
    path: /opt
    temporal_path: /tmp

Por último, en el fichero main.yml del directorio meta dentro del rol, tenemos las dependencias que tiene el este rol. En el tutorial mencionado anteriormente también hablo sobre la gestión de dependencias de los roles de ansible.

roles/kubernetes/meta/main.yml
---

# file: roles/kubernetes/kubernetes/meta/main.yml

dependencies:
    - {role: docker}

3.2. Docker

Nos queda crear las tareas pertenecientes al rol docker, que instalarán la última versión de Docker.

roles/docker/tasks/main.yml
---

# file: roles/docker/tasks/main.yml

- include: docker_prerequisites.yml
- include: docker_setup.yml
- include: docker_install.yml

Como bien dice, estos ficheros contienen las tareas encargadas de los prerequisitos de la instalacción, la configuración y la instalación propiamente dicha.

roles/docker/tasks/docker_prerequisites.yml
---

# file: roles/docker/tasks/docker_prerequisites.yml

- name: install Ensure that apt works with https method and CA certificates are installed
  apt: >
    name="{{ item }}"
    state=latest
    update_cache=yes
  with_items:
      - apt-transport-https
      - ca-certificates

- name: Add GPG key
  command: sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

- name: Copy docker.list
  copy: >
    src=docker.list
    dest=/etc/apt/sources.list.d
    force=yes

- name: Install appsrmor package
  apt: >
    name=apparmor
    update_cache=yes

roles/docker/tasks/docker_setup.yml
---

# file: roles/docker/tasks/docker_setup.yml

- name: create docker group
  group: name=docker state=present

- name: create docker user
  user: name=docker group=docker state=present
roles/docker/tasks/docker_install.yml
---

# file: roles/docker/tasks/docker_install.yml

- name: install docker
  apt: >
    name="docker-engine"
    state=latest
    update_cache=yes


- name: ensure docker is running and enabled as service
  become_user: docker
  service: name=docker state=started enabled=yes

Con esto ya tendríamos preparado el entorno y la configuración.

4. Comprobación

Una vez realizada la configuración, basta con ejecutar el comando vagrant up para comprobar como se despliega. Una vez termine vagrant, comprobamos como accediendo a la url localhost:8080 tenemos la indicaciones del api rest. El dashboard viene deshabilitado por defecto, pero veremos como habilitarlo en el próximo tutorial.

5. Conclusiones

Comprobamos una vez más como gracias a Ansible crear una infraestructura compleja como instalar Kubernetes en ubuntu puede ser hecho con un solo comando. Puedes ver los ficheros creados en el tutorial en mi repositorio de github.

6. Referencias

  • Documentación de Kubernetes para instalación de Ubuntu.

2 COMENTARIOS

  1. Buenas Juan Antonio.

    Me ha parecido muy interesante el tutorial, de hecho lo he estado siguiendo para instalarlo en mi entorno y tengo algunas puntualizaciones respecto al fichero roles/kubernetes/tasks/main.yml.
    Primero en Fedora 23 con ansible 2.0.2.0 o con ansible 1.9.6 falla al descomprimir los archivos de kubernetes. El fallo está relacionado con https://github.com/ansible/ansible-modules-core/issues/2936, para solucionarlo he tenido que descomprimir con tar los archivos en vez de usar el módulo.
    Otro problema que me he encontrado es que a la hora de ejecutar una orden la comprobación para ver si hay errores falla. Tanto a la hora de mover la carpeta como a la hora de iniciar el cluster añades un not en la clausula y hace que falle.

    Te copio aquí mi fichero:

    # file: roles/kubernetes/tasks/main.yml

    – name: Create ssh key for this machine (Necesary to start kubernetes)
    user: name=root generate_ssh_key=yes
    register: user_var

    – name: Add key to authorized keys
    shell: «cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys»

    – name: Download Kubernetes release bundle
    get_url: >
    url=»https://github.com/GoogleCloudPlatform/kubernetes/releases/download/v1.2.0/kubernetes.tar.gz»
    dest=»{{kubernetes.temporal_path}}/kubernetes.tar.gz»

    – name: Untar the kubernetes bundle
    command: tar zxvf «{{kubernetes.temporal_path}}/kubernetes.tar.gz» -C {{kubernetes.path}}

    – name: Untar saltbase bundle to configure the cluster.
    command: tar zxvf «{{kubernetes.path}}/kubernetes/server/kubernetes-salt.tar.gz» -C {{kubernetes.path}}/kubernetes/cluster

    – name: move saltbase directory outside the unarchive directory
    shell: mv {{kubernetes.path}}/kubernetes/cluster/kubernetes/saltbase {{kubernetes.path}}/kubernetes/cluster
    register: result
    failed_when: «‘Directory not empty’ in result.stderr and ‘true’ in result.failed»

    – name: Delete empty directory
    file: >-
    path={{kubernetes.path}}/kubernetes/cluster/kubernetes
    state=absent

    – name: Setup default configuration
    copy: >
    src=config-default.sh
    dest={{kubernetes.path}}/kubernetes/cluster/ubuntu
    mode=»0755″
    force=yes

    – name: Start cluster
    shell: KUBERNETES_PROVIDER=ubuntu ./kube-up.sh chdir={{kubernetes.path}}/kubernetes/cluster
    register: result
    failed_when: «‘Text file busy’ in result.stdout and ‘true’ in result.failed»

    – name: Add kube-system namespace
    shell: ./kubectl create -f /opt/kubernetes/cluster/ubuntu/namespace.yaml chdir=/opt/kubernetes/cluster/ubuntu/binaries
    ignore_errors: yes

    Un saludo y gracias por el tutorial.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad