Instalación de Contenedores con Dockers, Podman y Kubernetes en Linux Centos

Tabla de contenidos

¿Qué son los contenedores?

Los Dockers, PODs o Contenedores son una tecnología que permite ejecutar una aplicación, independientemente del sistema operativo que haya detrás, con el mínimo de recursos posible, de forma aislada y con alta disponibilidad si utilizamos algún software como Kubernetes, Openshift o Swarm.

Un contenedor se puede mover a otro host diferente, lo que facilita la migración de aplicaciones a otros servidores más actualizados.

Administración de contenedores con DOCKERS

Instalación Dockers en Linux

Para esta guía estoy probando Dockers en un Linux Centos 7.5, concretamente en esta versión:

[[email protected] ~]# lsb_release -a
 LSB Version: :core-4.1-amd64:core-4.1-noarch
 Distributor ID: CentOS
 Description: CentOS Linux release 7.5.1804 (Core) 
 Release: 7.5.1804
 Codename: Core
 [[email protected] ~]#

Lo primero que he hecho ha sido instalar el producto con el comando yum install docker. Para ello, he tenido que habilitar el repositorio Centos-Extras, añadiendo la directiva enabled=1 en el fichero /etc/yum.repos.d/CentOS-Base.repo.

[[email protected] ~]# rpm -qa |grep -i docker
 docker-common-1.13.1-75.git8633870.el7.centos.x86_64
 docker-client-1.13.1-75.git8633870.el7.centos.x86_64
 docker-1.13.1-75.git8633870.el7.centos.x86_64
 [[email protected] ~]#

A continuación, he habilitado y arrancado el servicio:

[[email protected] ~]# systemctl enable docker
 Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
[[email protected] ~]#

[[email protected] ~]# systemctl start docker
[[email protected] ~]#

 [[email protected] ~]# systemctl status docker
 ● docker.service - Docker Application Container Engine
 Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
 Active: active (running) since Wed 2018-10-31 12:18:48 CET; 4s ago
 Docs: http://docs.docker.com
 Main PID: 3481 (dockerd-current)
 Tasks: 17
 CGroup: /system.slice/docker.service
 ├─3481 /usr/bin/dockerd-current --add-runtime docker-runc=/usr/libexec/do...
 └─3486 /usr/bin/docker-containerd-current -l unix:///var/run/docker/libco...
Oct 31 12:18:47 Centos7 dockerd-current[3481]: time="2018-10-31T12:18:47.776905246+..."
 Oct 31 12:18:47 Centos7 dockerd-current[3481]: time="2018-10-31T12:18:47.817330457+..."
 Oct 31 12:18:47 Centos7 dockerd-current[3481]: time="2018-10-31T12:18:47.818285710+..."
 Oct 31 12:18:47 Centos7 dockerd-current[3481]: time="2018-10-31T12:18:47.838045198+..."
 Oct 31 12:18:48 Centos7 dockerd-current[3481]: time="2018-10-31T12:18:48.065625910+..."
 Oct 31 12:18:48 Centos7 dockerd-current[3481]: time="2018-10-31T12:18:48.258164231+..."
 Oct 31 12:18:48 Centos7 dockerd-current[3481]: time="2018-10-31T12:18:48.304388542+..."
 Oct 31 12:18:48 Centos7 dockerd-current[3481]: time="2018-10-31T12:18:48.304428927+...1
 Oct 31 12:18:48 Centos7 dockerd-current[3481]: time="2018-10-31T12:18:48.317518495+..."
 Oct 31 12:18:48 Centos7 systemd[1]: Started Docker Application Container Engine.
 Hint: Some lines were ellipsized, use -l to show in full.
 [[email protected] ~]#

Arrancando nuestro primer contenedor

Con el comando docker search «Nombre_del_contenedor» podemos encontrar todos los contenedores disponibles en el repositorio oficial de DockerHub.

Docker search

O también los podemos encontrar vía WEB:

Búsqueda de un contenedor en DockerHub

Imaginemos que nos interesa instalar el contenedor llamado «httpd». Lo haremos con el siguiente comando docker pull httpd:

docker pull

Y comprobamos que hemos descargado la imagen del contenedor correctamente con el comando docker images:

docker images

Por fin llegamos al punto clave de esta sección: Arrancar el contenedor. Lo haremos con el comando docker run -dit –name apachetest -p 8080:80 -v /tmp/ws/:/usr/local/apache2/htdocs/ httpd.

Lo que estamos haciendo aquí es:

  • El nombre del contenedor será apachetest.
  • Mapeamos el puerto local 8080 hacia el puerto 80 interno del contenedor.
  • Mapeamos el directorio local /tmp/ws al directorio interno del contenedor /usr/local/apache2/htdocs, que es el DocumentRoot del servidor de Apache del contenedor.

También voy a crear un fichero local que será cargado por el servidor de Apache del contenedor:

[[email protected] ~]# cat /tmp/ws/index.html 
 <html>
 Soy un Webserver corriendo en un contenedor
 </html>
 [[email protected] ~]#

docker run apachetest

Con el comando docker ps comprobaremos todos los contenedores que tenemos en ejecución:

docker ps

Con el comando docker inspect ID, por ejemplo docker inspect 9b74aaddb3fd obtendremos la configuración del pod. Ejemplo:

[[email protected] containers]# podman inspect localhost/david/apache
[
    {
        "Id": "313ed872ea33685b86bd773df422260ae68169a99445844703e89c8766ba307b",
        "Digest": "sha256:33ba41f2709074dbcf8a61907ccc7869d046ffe045363b731d1c78d3734b6fca",
        "RepoTags": [
            "localhost/david/apache:latest",
            "localhost/david/apache:v1"
        ],
        "RepoDigests": [
            "localhost/david/[email protected]:33ba41f2709074dbcf8a61907ccc7869d046ffe045363b731d1c78d3734b6fca"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2021-12-27T07:50:58.119283995Z",
        "Config": {
            "ExposedPorts": {
                "80/tcp": {}
            },
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "httpd",
                "-D",
                "FOREGROUND"
            ],
            "Labels": {
                "description": "Contenedor personalizado de HTTP de David Martinez",
                "io.buildah.version": "1.21.3",
                "org.label-schema.build-date": "20210915",
                "org.label-schema.license": "GPLv2",
                "org.label-schema.name": "CentOS Base Image",
                "org.label-schema.schema-version": "1.0",
                "org.label-schema.vendor": "CentOS"
            }
        },
        "Version": "",
        "Author": "David Martinez \[email protected]\u003e",
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 260651761,
        "VirtualSize": 260651761,
        "GraphDriver": {
            "Name": "overlay",
            "Data": {
                "LowerDir": "/var/lib/containers/storage/overlay/74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59/diff",
                "UpperDir": "/var/lib/containers/storage/overlay/26a183124afda6552bc75d15ce5fd419197de116c6b9f07b04002ffac1c6df6d/diff",
                "WorkDir": "/var/lib/containers/storage/overlay/26a183124afda6552bc75d15ce5fd419197de116c6b9f07b04002ffac1c6df6d/work"
            }
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59",
                "sha256:6a20016d0ad82ee39608a5833aaecb4d480c4cde1f6b703ecb33311aef15e0c2"
            ]
        },
        "Labels": {
            "description": "Contenedor personalizado de HTTP de David Martinez",
            "io.buildah.version": "1.21.3",
            "org.label-schema.build-date": "20210915",
            "org.label-schema.license": "GPLv2",
            "org.label-schema.name": "CentOS Base Image",
            "org.label-schema.schema-version": "1.0",
            "org.label-schema.vendor": "CentOS"
        },
        "Annotations": {},
        "ManifestType": "application/vnd.oci.image.manifest.v1+json",
        "User": "",
        "History": [
            {
                "created": "2021-09-15T18:20:04.026056566Z",
                "created_by": "/bin/sh -c #(nop) ADD file:805cb5e15fb6e0bb0326ca33fd2942e068863ce2a8491bb71522c652f31fb466 in / "
            },
            {
                "created": "2021-09-15T18:20:04.987853959Z",
                "created_by": "/bin/sh -c #(nop)  LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20210915",
                "empty_layer": true
            },
            {
                "created": "2021-09-15T18:20:05.184694267Z",
                "created_by": "/bin/sh -c #(nop)  CMD [\"/bin/bash\"]",
                "empty_layer": true
            },
            {
                "created": "2021-12-27T07:50:46.028720313Z",
                "created_by": "/bin/sh -c #(nop) MAINTAINER David Martinez \[email protected]\u003e",
                "empty_layer": true
            },
            {
                "created": "2021-12-27T07:50:46.028835369Z",
                "created_by": "/bin/sh -c #(nop) LABEL description=\"Contenedor personalizado de HTTP de David Martinez\"",
                "empty_layer": true
            },
            {
                "created": "2021-12-27T07:50:57.544487944Z",
                "created_by": "/bin/sh -c yum install -y httpd \u0026\u0026 yum clean all",
                "empty_layer": true
            },
            {
                "created": "2021-12-27T07:50:58.115090713Z",
                "created_by": "/bin/sh -c echo \"Servidor HTTP de David Martinez\" \u003e /var/www/html/index.html",
                "empty_layer": true
            },
            {
                "created": "2021-12-27T07:50:58.115563663Z",
                "created_by": "/bin/sh -c #(nop) EXPOSE 80",
                "empty_layer": true
            },
            {
                "created": "2021-12-27T07:50:59.000445052Z",
                "created_by": "/bin/sh -c #(nop) CMD [\"httpd\", \"-D\", \"FOREGROUND\"]",
                "author": "David Martinez \[email protected]\u003e",
                "comment": "FROM docker.io/library/centos:latest"
            }
        ],
        "NamesHistory": [
            "localhost/david/apache:v1",
            "localhost/david/apache:latest"
        ]
    }
]
[[email protected] containers]# 

Eliminar un contenedor

Para finalizar, vamos a eliminar el contenedor que hemos creado.

Primeramente lo pararemos con el comando docker stop.

docker stop

Ahora ya lo podemos eliminar con el comando docker rm ID.

docker rm

Eliminar la imagen de un contenedor

Si no vamos a arrancar más contenedores basados en la misma imagen del contenedor, la eliminaremos con el comando docker rmi ID.

docker remove image

Administración de contenedores con PODMAN

En el punto anterior hemos hablado sobre el funcionamiento de contenedores en Linux con Dockers. Sin embargo, Docker es una empresa privada, lo que significa que su código no es «opensource».

Para solucionar esta casuística, RedHat creó Podman, una herramienta de código abierto para administrar contenedores o PODs en Linux. Podman realiza las mismas funciones que Dockers, por lo que no me extenderé demasiado habiendo hablado del tema en este mismo artículo.

En cualquier caso, vamos a echar un vistazo a las características principales con algunos ejemplos:

Instalación de Podman

Con el comando habitual de Linux RedHat 8, dnf, instalaremos podman y luego habilitaremos el servicio:

[[email protected] ~]# dnf install -y podman

[[email protected] ~]# rpm -qa |grep -i podman
podman-catatonit-3.0.1-7.module_el8.4.0+830+8027e1c4.x86_64
podman-3.0.1-7.module_el8.4.0+830+8027e1c4.x86_64
[[email protected] ~]# systemctl start podman

Acceso al registro de imágenes de contenedores (PODs)

En el fichero /etc/containers/registries.conf tenemos configurados las URLs de acceso a las imágenes de los contenedores pero también podemos crear nuestro propio fichero en $HOME/.config/containers/registries.conf.

[[email protected] ~]# cat /etc/containers/registries.conf |grep -v "#" |grep -v ^$
[registries.search]
registries = ['registry.access.redhat.com', 'registry.redhat.io', 'docker.io']
[registries.insecure]
registries = []
[registries.block]
registries = []
unqualified-search-registries = ["registry.fedoraproject.org", "registry.access.redhat.com", "registry.centos.org", "docker.io"]
[[email protected] ~]# 

Si a alguno de los registros necesita usuario y contraseña para acceder a él, utilizaremos el comando «podman login»:

# podman login <servidor>:5000
Enter Username:Mi_Usuario
Enter Password:Mi_Contraseña
Login Succeeded!

También podemos hacerlo por parámetros:

# podman login -u admin -p ContraseñaSecreta registry.access.redhat.com
# podman pull registry.lab.example.com/rhel8/httpd-24:latest

Listado de imágenes del repositorio

Una vez que ya tenemos acceso al repositorio de imágenes, podremos sacar un listado de las que hay disponibles. Pongamos por caso que nos interesa encontrar una imagen de Apache:

[[email protected] ~]# podman search httpd
INDEX       NAME                                                                         DESCRIPTION                                      STARS   OFFICIAL  AUTOMATED
redhat.com  registry.access.redhat.com/rhscl/httpd-24-rhel7                              Apache HTTP 2.4 Server                           0                 
redhat.com  registry.access.redhat.com/cloudforms46-beta/cfme-openshift-httpd            CloudForms is a management and automation pl...  0                 
redhat.com  registry.access.redhat.com/cloudforms46/cfme-openshift-httpd                 Web Server image for a multi-pod Red Hat® C...   0                 

Consejo: Descargar la imagen con mejor puntuación (o más estrellas):

docker.io docker.io/library/httpd The Apache HTTP Server Project 3618 [OK]

Descargar una imagen

Con podman pull descargaremos la imagen del registro que nos interesa:

[[email protected] ~]# podman pull docker.io/library/httpd 
Trying to pull docker.io/library/httpd:latest...
Getting image source signatures
Copying blob d74938eee980 done  
Copying blob a8c75048351a done  
Copying blob 33847f680f63 done  
Copying blob 763d74736d95 done  
Copying blob 963cfdce5a0c done  
Copying config bde40dcb22 done  
Writing manifest to image destination
Storing signatures
bde40dcb22a7a82392a45bcc7f191bb2f807d57711934d4eda869d3d649a3643
[[email protected] ~]# 

[[email protected] ~]# podman image list
REPOSITORY               TAG     IMAGE ID      CREATED     SIZE
docker.io/library/httpd  latest  bde40dcb22a7  2 days ago  142 MB
[[email protected] ~]# 

Exportar una imagen que tenemos ya creada a formato tar

Si queremos guardar una imagen de un contenedor que ya tenemos en nuestro sistema, a un fichero tar, lo haremos de la siguiente manera:

[[email protected] ~]# podman images |egrep "REPO|david"
REPOSITORY                 TAG         IMAGE ID      CREATED       SIZE
localhost/david/apache     latest      313ed872ea33  9 days ago    261 MB
localhost/david/apache     v1          313ed872ea33  9 days ago    261 MB
[[email protected] ~]# podman save -o apache_david_v1.tar localhost/david/apache
Getting image source signatures
Copying blob 74ddd0ec08fa done  
Copying blob 6a20016d0ad8 done  
Copying config 313ed872ea done  
Writing manifest to image destination
Storing signatures
[[email protected] ~]# ls -la apache_david_v1.tar 
-rw-r--r-- 1 root root 260661248 Jan  5 09:36 apache_david_v1.tar
[[email protected] ~]# 

Podremos cargar un contenedor guardado en un fichero tar con el comando:

podman load -i fichero.tar

Si esta imagen la quisiéramos subir a DockerHub, por ejemplo, podríamos hacerlo con nuestra cuenta:

podman login docker.io
podman tag localhost/david/apache:v1 docker.io/david/apache:v1
podman push docker.io/david/apache:v1

Arrancar un contenedor

En el siguiente ejemplo arrancamos el POD docker.io/library/httpd por el puerto 8080 local en nuestro host e indicamos que el directorio local del host /apache2 se mapee como /apache dentro del contenedor:

[[email protected] ~]# podman run -d -p 8080:80 --name apachetest -v /apache2:/apache docker.io/library/httpd
e25c7b7957410858b7d150fb71a55fdea83162e2944d0bcac75b6af595161810
[[email protected] ~]# 

Si quisiéramos pasarle algunas variables de entorno al contenedor, por ejemplo, el usuario y la contraseña de MySQL a un contenedor de MySQL, lo haríamos de la siguiente manera:

podman run -d --name mydb -p 30306:3306 -e MYSQL_ROOT_PASSWORD=Contraseña_Secreta -e MYSQL_USER=usuariobd -e MYSQL_PASSWORD=Contraseña_usuariobd -e MYSQL_DATABASE=agenda

Parar y eliminar un contenedor

[[email protected] ~]# podman container list --all
CONTAINER ID  IMAGE                    COMMAND           CREATED        STATUS                         PORTS                   NAMES
e25c7b795741  docker.io/library/httpd  httpd-foreground  2 minutes ago  Exited (0) About a minute ago  0.0.0.0:8080->8000/tcp  apachetest
[[email protected] ~]# podman container rm e25c7b795741
e25c7b7957410858b7d150fb71a55fdea83162e2944d0bcac75b6af595161810
[[email protected] ~]# 

Parar y eliminar un POD con su nombre en vez de con su ID

Lo primero que vamos a hacer es arrancar un POD con un nombre personalizado. Para este ejemplo, lo llamaremos «podman_test»:

[[email protected] ~]# podman run -p 9990:9990 -p 4447:4447 -p 9999:9999 -p 8080:8080 -d --name podman_test docker.io/tutum/jboss
ebd06383e3ed1de4f0d6f4abd070fba16c5d96fce25287f07ca95431629552fb
[[email protected] ~]# podman ps
CONTAINER ID  IMAGE                         COMMAND     CREATED        STATUS            PORTS                                                                                           NAMES
ebd06383e3ed  docker.io/tutum/jboss:latest  /run.sh     3 seconds ago  Up 3 seconds ago  0.0.0.0:4447->4447/tcp, 0.0.0.0:8080->8080/tcp, 0.0.0.0:9990->9990/tcp, 0.0.0.0:9999->9999/tcp  podman_test
[[email protected] ~]# 

A continuación, lo pararemos llamando a su nombre personalizado:

[[email protected] ~]# podman stop podman_test
podman_test
[[email protected] ~]# podman ps
CONTAINER ID  IMAGE       COMMAND     CREATED     STATUS      PORTS       NAMES
[[email protected] ~]# 

TRUCO: Con «podman ps –format» podemos mostrar en pantalla solamente los campos que nos interesen, por ejemplo, para hacer algún script:

[[email protected] ~]# podman run -p 8080:80 -d localhost/david/apache:latest
1b9dd33882dc07a83e1727569111e7027d5c73bbac28d4d7437a1d5889428c4b
[[email protected] ~]#

[[email protected] ~]# podman ps
CONTAINER ID  IMAGE                          COMMAND               CREATED        STATUS            PORTS                 NAMES
1b9dd33882dc  localhost/david/apache:latest  httpd -D FOREGROU...  4 seconds ago  Up 4 seconds ago  0.0.0.0:8080->80/tcp  dazzling_villani
[[email protected] ~]#

[[email protected] ~]# podman ps --format="{{.ID}} {{.Names}} {{.Status}}"
1b9dd33882dc dazzling_villani Up 20 seconds ago
[[email protected] ~]# 

Eliminar una imagen

[[email protected] ~]# podman image list
REPOSITORY               TAG     IMAGE ID      CREATED     SIZE
docker.io/library/httpd  latest  bde40dcb22a7  2 days ago  142 MB
[[email protected] ~]# podman image rm bde40dcb22a7
Untagged: docker.io/library/httpd:latest
Deleted: bde40dcb22a7a82392a45bcc7f191bb2f807d57711934d4eda869d3d649a3643
[[email protected] ~]# 

Crear nuestra propia imagen personalizada con Podman

Supongamos que para nuestro proyecto necesitamos crear una imagen personalizada de Apache. Para hacerlo con Podman, seguiremos las siguientes instrucciones:

  • Creamos un fichero llamado «DockerFile» con el siguiente contenido:
[[email protected] httpd-david]# pwd
/root/Continers/httpd-david
[[email protected] httpd-david]# cat Dockerfile 
FROM docker.io/library/centos
MAINTAINER David Martinez <[email protected]>
LABEL description="Contenedor personalizado de HTTP de David Martinez"
RUN yum install -y httpd && yum clean all
RUN echo "Servidor HTTP de David Martinez" > /var/www/html/index.html
EXPOSE 80
CMD ["httpd", "-D", "FOREGROUND"]
[[email protected] httpd-david]#

Si quisiéramos exportar más de un puerto, los escribiríamos uno detrás de otro. Por ejemplo:

EXPOSE 80 8080 9090

Podemos observar que:

  • La imagen está basada en Centos
  • El autor es David Martínez
  • Hemos añadido una descripción al contenedor
  • Instalamos el RPM «httpd» y luego hacemos una limpieza de yum
  • Creamos el fichero «index.html» con un contenido personalizado
  • Exportamos o exponemos, como se dice en el mundo de contenedores, el puerto 80
  • Ejecutamos el comando «httpd -D FOREGROUND» al arrancar el contenedor

  • El siguiente paso es construir la imagen del contenedor:
[[email protected] httpd-david]# podman build --layers=false -t david/apache .
STEP 1: FROM docker.io/library/centos
Trying to pull docker.io/library/centos:latest...
Getting image source signatures
Copying blob a1d0c7532777 done  
Copying config 5d0da3dc97 done  
Writing manifest to image destination
Storing signatures
STEP 2: MAINTAINER David Martinez <[email protected]>
STEP 3: LABEL description="Contenedor personalizado de HTTP de David Martinez"
STEP 4: RUN yum install -y httpd && yum clean all
CentOS Linux 8 - AppStream                      3.9 MB/s | 8.4 MB     00:02    
CentOS Linux 8 - BaseOS                         3.4 MB/s | 3.6 MB     00:01    
CentOS Linux 8 - Extras                          16 kB/s |  10 kB     00:00    
Dependencies resolved.
================================================================================
 Package          Arch   Version                                Repo       Size
================================================================================
Installing:
 httpd            x86_64 2.4.37-43.module_el8.5.0+1022+b541f3b1 appstream 1.4 M
Installing dependencies:
 apr              x86_64 1.6.3-12.el8                           appstream 129 k
 apr-util         x86_64 1.6.1-6.el8                            appstream 105 k
 brotli           x86_64 1.0.6-3.el8                            baseos    323 k
 centos-logos-httpd
                  noarch 85.8-2.el8                             baseos     75 k
 httpd-filesystem noarch 2.4.37-43.module_el8.5.0+1022+b541f3b1 appstream  39 k
 httpd-tools      x86_64 2.4.37-43.module_el8.5.0+1022+b541f3b1 appstream 107 k
 mailcap          noarch 2.1.48-3.el8                           baseos     39 k
 mod_http2        x86_64 1.15.7-3.module_el8.4.0+778+c970deab   appstream 154 k
Installing weak dependencies:
 apr-util-bdb     x86_64 1.6.1-6.el8                            appstream  25 k
 apr-util-openssl x86_64 1.6.1-6.el8                            appstream  27 k
Enabling module streams:
 httpd                   2.4                                                   

Transaction Summary
================================================================================
Install  11 Packages

Total download size: 2.4 M
Installed size: 7.1 M
Downloading Packages:
(1/11): apr-util-bdb-1.6.1-6.el8.x86_64.rpm     103 kB/s |  25 kB     00:00    
(2/11): apr-util-openssl-1.6.1-6.el8.x86_64.rpm 717 kB/s |  27 kB     00:00    
(3/11): apr-util-1.6.1-6.el8.x86_64.rpm         344 kB/s | 105 kB     00:00    
(4/11): apr-1.6.3-12.el8.x86_64.rpm             415 kB/s | 129 kB     00:00    
(5/11): httpd-filesystem-2.4.37-43.module_el8.5 1.0 MB/s |  39 kB     00:00    
(6/11): httpd-tools-2.4.37-43.module_el8.5.0+10 2.5 MB/s | 107 kB     00:00    
(7/11): mod_http2-1.15.7-3.module_el8.4.0+778+c 1.9 MB/s | 154 kB     00:00    
(8/11): centos-logos-httpd-85.8-2.el8.noarch.rp 376 kB/s |  75 kB     00:00    
(9/11): mailcap-2.1.48-3.el8.noarch.rpm         528 kB/s |  39 kB     00:00    
(10/11): brotli-1.0.6-3.el8.x86_64.rpm          874 kB/s | 323 kB     00:00    
(11/11): httpd-2.4.37-43.module_el8.5.0+1022+b5 3.0 MB/s | 1.4 MB     00:00    
--------------------------------------------------------------------------------
Total                                           2.1 MB/s | 2.4 MB     00:01     
warning: /var/cache/dnf/appstream-02e86d1c976ab532/packages/apr-1.6.3-12.el8.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID 8483c65d: NOKEY
CentOS Linux 8 - AppStream                      1.6 MB/s | 1.6 kB     00:00    
Importing GPG key 0x8483C65D:
 Userid     : "CentOS (CentOS Official Signing Key) <[email protected]>"
 Fingerprint: 99DB 70FA E1D7 CE22 7FB6 4882 05B5 55B3 8483 C65D
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
Key imported successfully
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                        1/1 
  Installing       : apr-1.6.3-12.el8.x86_64                               1/11 
  Running scriptlet: apr-1.6.3-12.el8.x86_64                               1/11 
  Installing       : apr-util-bdb-1.6.1-6.el8.x86_64                       2/11 
  Installing       : apr-util-openssl-1.6.1-6.el8.x86_64                   3/11 
  Installing       : apr-util-1.6.1-6.el8.x86_64                           4/11 
  Running scriptlet: apr-util-1.6.1-6.el8.x86_64                           4/11 
  Installing       : httpd-tools-2.4.37-43.module_el8.5.0+1022+b541f3b1    5/11 
  Installing       : mailcap-2.1.48-3.el8.noarch                           6/11 
  Installing       : centos-logos-httpd-85.8-2.el8.noarch                  7/11 
  Installing       : brotli-1.0.6-3.el8.x86_64                             8/11 
  Running scriptlet: httpd-filesystem-2.4.37-43.module_el8.5.0+1022+b54    9/11 
  Installing       : httpd-filesystem-2.4.37-43.module_el8.5.0+1022+b54    9/11 
  Installing       : mod_http2-1.15.7-3.module_el8.4.0+778+c970deab.x86   10/11 
  Installing       : httpd-2.4.37-43.module_el8.5.0+1022+b541f3b1.x86_6   11/11 
  Running scriptlet: httpd-2.4.37-43.module_el8.5.0+1022+b541f3b1.x86_6   11/11 
  Verifying        : apr-1.6.3-12.el8.x86_64                               1/11 
  Verifying        : apr-util-1.6.1-6.el8.x86_64                           2/11 
  Verifying        : apr-util-bdb-1.6.1-6.el8.x86_64                       3/11 
  Verifying        : apr-util-openssl-1.6.1-6.el8.x86_64                   4/11 
  Verifying        : httpd-2.4.37-43.module_el8.5.0+1022+b541f3b1.x86_6    5/11 
  Verifying        : httpd-filesystem-2.4.37-43.module_el8.5.0+1022+b54    6/11 
  Verifying        : httpd-tools-2.4.37-43.module_el8.5.0+1022+b541f3b1    7/11 
  Verifying        : mod_http2-1.15.7-3.module_el8.4.0+778+c970deab.x86    8/11 
  Verifying        : brotli-1.0.6-3.el8.x86_64                             9/11 
  Verifying        : centos-logos-httpd-85.8-2.el8.noarch                 10/11 
  Verifying        : mailcap-2.1.48-3.el8.noarch                          11/11 

Installed:
  apr-1.6.3-12.el8.x86_64                                                       
  apr-util-1.6.1-6.el8.x86_64                                                   
  apr-util-bdb-1.6.1-6.el8.x86_64                                               
  apr-util-openssl-1.6.1-6.el8.x86_64                                           
  brotli-1.0.6-3.el8.x86_64                                                     
  centos-logos-httpd-85.8-2.el8.noarch                                          
  httpd-2.4.37-43.module_el8.5.0+1022+b541f3b1.x86_64                           
  httpd-filesystem-2.4.37-43.module_el8.5.0+1022+b541f3b1.noarch                
  httpd-tools-2.4.37-43.module_el8.5.0+1022+b541f3b1.x86_64                     
  mailcap-2.1.48-3.el8.noarch                                                   
  mod_http2-1.15.7-3.module_el8.4.0+778+c970deab.x86_64                         

Complete!
21 files removed
STEP 5: RUN echo "Servidor HTTP de David Martinez" > /var/www/html/index.html
STEP 6: EXPOSE 80
STEP 7: CMD ["httpd", "-D", "FOREGROUND"]
STEP 8: COMMIT david/apache
Getting image source signatures
Copying blob 74ddd0ec08fa skipped: already exists  
Copying blob 6a20016d0ad8 done  
Copying config 313ed872ea done  
Writing manifest to image destination
Storing signatures
--> 313ed872ea3
Successfully tagged localhost/david/apache:latest
313ed872ea33685b86bd773df422260ae68169a99445844703e89c8766ba307b
[[email protected] httpd-david]# 
  • Comprobamos que la imagen se ha creado correctamente:
[[email protected] httpd-david]# podman images |egrep "REPOSITORY|david"
REPOSITORY                 TAG         IMAGE ID      CREATED             SIZE
localhost/david/apache     latest      313ed872ea33  About a minute ago  261 MB
[[email protected] httpd-david]# 
  • Por último, arrancamos el contenedor y comprobamos que se muestra el contenido personalizado en el index.html:
[[email protected] httpd-david]# podman run --name david-apache-httpd-test -d -p 1082:80 localhost/david/apache
0243c9d23592150107503512c112405ec2df5ab9958a850f630e1e1773125080
[[email protected] httpd-david]# 

CONTAINER ID  IMAGE                          COMMAND               CREATED         STATUS             PORTS                 NAMES
0243c9d23592  localhost/david/apache:latest  httpd -D FOREGROU...  57 seconds ago  Up 57 seconds ago  0.0.0.0:1082->80/tcp  david-apache-httpd-test
[[email protected] httpd-david]# 


[[email protected] httpd-david]# curl -s http://localhost:1082
Servidor HTTP de David Martinez
[[email protected] httpd-david]# 

Con el comando «man podman-run» obtenemos ayuda de todos los parámetros de podman para ejecutar contenedores.

  Tutorial de Kibana - Instalación y configuración

Copiar un fichero dentro del contenedor

Cuando construyamos la imagen de DockerFile y debamos copiar un fichero dentro del contenedor, dicho fichero ha de existir dentro del path donde construimos la imagen. Es decir, no podemos copiar un fichero que lo tengamos en el path absoluto del sistema /tmp, por ejemplo, o cualquier otro directorio. Debe estar en el path donde compilamos la imagen.

NO sería correcta la siguiente sintaxis:

ADD /.../.../<file name>.zip <destination dir>
 
o

COPY /.../.../<file name>.zip <destination dir>

Lo que es correcto es:

ADD <file name>.zip <destination dir>
 
o

COPY <file name>.zip <destination dir>

Con ADD podemos copiar ficheros ubicados en Internet. Por ejemplo:

ADD http://miservidorweb.com/archivo.pdf /var/www/html

Guía rápida de DockerFile

FROM

La imagen original en la que se basa la nueva que estamos creando:

FROM docker.io/library/centos

LABEL

Podemos crear etiquetas para organizar nuestro proyecto. Por ejemplo, control de versiones, licencias, etc. Añadiremos una etiqueta nueva para cada campo que deseemos controlar.

# Set one or more individual labels
LABEL com.ejemplo.version="0.0.1-beta"
LABEL vendor1="Empresa 1"
LABEL vendor2="Empresa 2"
LABEL com.ejemplo.release-date="2022-01-12"
LABEL com.ejemplo.version.is-production=""

RUN

Ejecuta un comando dentro de la imagen que estamos creando.

RUN yum install -y httpd && yum clean all

CMD

Ejecuta un comando durante el arranque del contenedor basándonos en la imagen que acabamos de crear.

CMD ["httpd", "-D", "FOREGROUND"]

ENTRYPOINT

Se utiliza para indicar el comando principal que va a ejecutar el contenedor cuando arranque. Luego, podemos utilizar CMD para especificar parámetros por defecto.

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

EXPOSE

Son los puertos de comunicaciones por los que escucha el contenedor.

EXPOSE 80
EXPOSE 80/tcp
EXPOSE 80/tcp 80/udp

ENV

Configuramos variables de entorno dentro del contenedor.

ENV VERSION=1.0
RUN echo $VERSION

ADD o COPY

Copia archivos ubicados en nuestro host dentro de la imagen que estamos generando. Este punto se ha comentado anteriormente.

VOLUME

Crea un punto de montaje dentro del contenedor a partir de un filesystem compartido desde el host externo dentro del contenedor.

FROM
 ubuntu
RUN mkdir /test
RUN echo "Hola Mundo" > /test/hola_mundo.txt
VOLUME /test

USER

Si no queremos que el usuario por defecto con el que arrancan los comandos del contenedor sea root, deberemos especificar el usuario que necesitamos.

USER <user>[:<group>]
USER
 <UID>[:<GID>]

WORKDIR

Mediante rutas absolutas, especificamos el directorio de trabajo sobre el que se ejecutarán los comandos del contenedor RUN, CMD, ENTRYPOINT, COPY y ADD.

DockerFile tiene muchas más instrucciones y parámetros, pero en esta guía rápida hemos visto las más importantes.

Obtener ayuda sobre la sintaxis de DockerFile

No existe un comando como «man DockerFile» en Linux RedHat 8 a día de hoy, por lo que si estás pensando en obtener ayuda durante el examen de certificación olvídate de eso.

Lo que sí que podemos hacer es instalar el paquete buildah-tests, que tiene un montón de ejemplos para crear imágenes de contenedores:

[[email protected] ~]# dnf install -y buildah-tests
...
[[email protected] system]# rpm -q --dump buildah-tests-1.22.3-2.module_el8.5.0+911+f19012f9.x86_64 |grep -i dockerfile
/usr/share/buildah/test/system/bud/add-chmod/Dockerfile 90 1629466139 b2ac3d9b4399fcf82ee81f359bf50ce79750fabe1c1742d78c10f63c30f851e5 0100644 root root 0 0 0 X
/usr/share/buildah/test/system/bud/add-chmod/Dockerfile.bad 90 1629466139 2707410ec92a50fc8ab31d99c39b2c6838c524571834a2118d1ab31fb63f06db 0100644 root root 0 0 0 X
/usr/share/buildah/test/system/bud/add-chmod/Dockerfile.combined 139 1629466139 df3e5fbc0702f9ad146f7d8dbfbba6cf872a95a812066124795e69540e30aacf 0100644 root root 0 0 0 X
/usr/share/buildah/test/system/bud/add-chown/Dockerfile 117 1629466139 c3320e42ffa9c73ad153dd4a7f08174a4b387419ba83723f061a049f681f1302 0100644 root root 0 0 0 X
/usr/share/buildah/test/system/bud/add-chown/Dockerfile.bad 117 1629466139 eb0708ea41dc7a372f19c6af414bb81742fcc0516716869be57c569d0fce3546 0100644 root root 0 0 0 X
/usr/share/buildah/test/system/bud/add-create-absolute-path/Dockerfile 131 1629466139 c009e75186c2f0e5a23ba10ecbd8d4a5bf96d69cae2f7ea7e64ddeefd3b229ab 0100644 root root 0 0 0 X
/usr/share/buildah/test/system/bud/add-create-relative-path/Dockerfile 96 1629466139 a639b355fe4825375f3d6c936dc63153233e9a19721cb1c49e226d02548a99a3 0100644 root root 0 0 0 X
/usr/share/buildah/test/system/bud/add-file/Dockerfile 99 1629466139 d925fa4590f68ff2c6a69d599a33348f60548a78db394e555f814a57299cb99a 0100644 root root 0 0 0 X
/usr/share/buildah/test/system/bud/addtl-tags/Dockerfile 23 1629466139 269cdcf06d0fabf1067e2ac8677d1ea55f8b3de4161bae11ad8a78242e533f90 0100644 root root 0 0 0 X

...


[[email protected] system]# cat /usr/share/buildah/test/system/bud/add-chmod/Dockerfile 
FROM alpine

ADD --chmod=777 addchmod.txt /tmp 
RUN ls -l /tmp/addchmod.txt 
CMD /bin/sh

[[email protected] system]# 

También podemos utilizar la guía oficial de DockerFile.

Modificar la etiqueta de una imagen

Supongamos que queremos modificar la etiqueta de la imagen que acabamos de crear, por ejemplo, para tener un control de versiones. Lo haríamos de la siguiente manera:

[[email protected] ~]# podman images
REPOSITORY                 TAG         IMAGE ID      CREATED       SIZE
localhost/david/apache     latest      313ed872ea33  9 days ago    261 MB
docker.io/roboxes/rhel8    latest      5fe6b544b5f5  12 days ago   971 MB
docker.io/library/mysql    latest      3218b38490ce  2 weeks ago   521 MB
docker.io/library/centos   latest      5d0da3dc9764  3 months ago  239 MB
docker.io/notnothin/rhel8  latest      3815a90e8c31  2 years ago   572 MB
[[email protected] ~]# podman tag localhost/david/apache localhost/david/apache:v1
[[email protected] ~]# podman images |grep david
localhost/david/apache     latest      313ed872ea33  9 days ago    261 MB
localhost/david/apache     v1          313ed872ea33  9 days ago    261 MB
[[email protected] ~]# 

Arrancar un contenedor con un usuario no privilegiado

Es una buena práctica para la seguridad del sistema operativo, no arrancar con root los contenedores, ya que una vulnerabilidad en un contenedor podría provocar que un atacante malintencionado se hiciese con el control del host.

Usuario ordinario del sistema

Lo que podemos hacer, es que éstos arranquen con un usuario no privilegiado. Para este ejemplo, utilizaremos el usuario «pods».

[[email protected] ~]# id pods
uid=1002(pods) gid=1003(pods) groups=1003(pods)
[[email protected] ~]# 

¿Por qué no debemos ejecutar nunca contenedores con el usuario root?

Cuando desde el host arrancamos un contenedor con el usuario root y dentro del contenedor arrancamos un proceso, en el host veremos que ese proceso se está ejecutando como root, lo cual, es un problema potencial de seguridad. Veamos un ejemplo:

[[email protected] ~]# podman run --rm -ti docker.io/notnothin/rhel8 /bin/bash
Trying to pull docker.io/notnothin/rhel8:latest...
Getting image source signatures
Copying blob f98b8d9fbeba done  
Copying config 3815a90e8c done  
Writing manifest to image destination
Storing signatures
bash-4.4# id
uid=0(root) gid=0(root) groups=0(root)
bash-4.4# sleep 1000

[[email protected] ~]# ps -ef |grep "sleep 1000" |grep -v grep
root        7715    7600  0 06:44 pts/0    00:00:00 sleep 1000
[[email protected] ~]# 

Ahora repetimos la misma operativa pero el contenedor lo vamos a ejecutar con el usuario «david»:

[[email protected] ~]$ podman run --rm -ti docker.io/notnothin/rhel8 /bin/bash
Trying to pull docker.io/notnothin/rhel8:latest...
Getting image source signatures
Copying blob f98b8d9fbeba done  
Copying config 3815a90e8c done  
Writing manifest to image destination
Storing signatures
bash-4.4# sleep 1000

[[email protected] ~]# ps -ef |grep "sleep 1000" |grep -v grep
david       7902    7890  0 06:46 pts/0    00:00:00 sleep 1000
[[email protected] ~]# 

Como podemos comprobar, en esta segunda ocasión, el proceso se está ejecutando como «david» y no como «root» en el host.

Número máximo de recursos del kernel a utilizar por cada usuario ordinario (namespaces)

Lo primero que vamos a hacer es configurar los «namespaces»:

[[email protected] ~]# echo "user.max_user_namespaces=28633" > /etc/sysctl.d/userns.conf
[[email protected] ~]# sysctl -p /etc/sysctl.d/userns.conf
user.max_user_namespaces = 28633
[[email protected] ~]# 

Los namespaces son una característica del kernel de Linux que sirve para que separar los recursos del kernel que ven diferentes procesos. Es decir, un conjunto de procesos ven un conjunto de recursos pero otro conjunto de procesos ven otro conjunto de recursos diferente.

La directiva max_user_namespaces significa que el número máximo de «espacios de nombres» (namespaces) que puede crear un usuario en el namespace actual.

Otro usuario diferente del sistema operativo tendrá el mismo número máximo de namespaces a crear y, así, sucesivamente.

Arranque de un POD de ejemplo con un usuario no privilegiado

Como comentaba anteriormente, arrancaremos el POD con el usuario «pods»:

[[email protected] ~]# su - pods
Last login: Wed Sep 15 09:02:27 CEST 2021 on pts/0
[[email protected] ~]$ podman run -d -p 8080:80 --name apachetest -v /apache2:/apache docker.io/library/httpd
Trying to pull docker.io/library/httpd:latest...
Getting image source signatures
Copying blob fe59ad2e7efe done  
Copying blob a330b6cecb98 [======================================] 25.9MiB / 25.9MiB
Copying blob 2cb26220caa8 done  
Copying blob 14e3dd65f04d done  
Copying blob 3138742bd847 done  
Copying config f34528d8e7 done  
Writing manifest to image destination
Storing signatures
70d90099108dce88173404d8d346def880812a8fa678345700baf319650c70df
[[email protected] ~]$


[[email protected] ~]$ podman container list --all
CONTAINER ID  IMAGE                                         COMMAND           CREATED         STATUS             PORTS                 NAMES
a8c917e46377  registry.access.redhat.com/ubi8/pause:latest                    8 days ago      Created                                  3a3b01ec4563-infra
eea1d756c951  registry.access.redhat.com/ubi8:latest        top               8 days ago      Created                                  container0
70d90099108d  docker.io/library/httpd:latest                httpd-foreground  22 seconds ago  Up 23 seconds ago  0.0.0.0:8080->80/tcp  apachetest
[[email protected] ~]$ 

Creación de un servicio de sistema para arrancar un POD con un usuario no privilegiado del sistema

Ahora que ya hemos comprobado que el contenedor arranca con el usuario «pods», lo que vamos a hacer a continuación es crear un servicio del sistema para que el contenedor arranque automáticamente con el boot del servidor.

El servicio lo crearemos con el usuario root, pero en las directivas del mismo ya indicamos que el usuario propietario es «pods»:

[[email protected] ~]# cat /etc/systemd/system/pods.service
[Unit]
Description=Contenedor arrancado por el usuario pods
After=network.target

[Service]
User=pods
Group=pods
Type=simple
TimeoutStartSec=5m
ExecStart=podman run -d -p 8080:80 --name apachetest -v /apache2:/apache docker.io/library/httpd
ExecReload=podman stop "apachetest"
ExecReload=podman rm -f "apachetest"
ExecStop=podman stop "apachetest"
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target
[[email protected] ~]# systemctl daemon-reload 
[[email protected] ~]# 

Alta disponibilidad de contenedores con KUBERNETES

Si vamos a trabajar en entornos profesionales, necesitamos configurar el servicio con alta disponibilidad para que los usuarios finales no se vean afectados en caso de incidencia con alguno de los nodos. Esto lo vamos a conseguir con Kubernetes.

Conceptos básicos de Kubernetes

Kuberntes es un sistema de clusters de contenedores de código abierto desarrollado por Google. Básicamente, configuraremos la cantidad de contenedores que queremos que se ejecuten simultáneamente y en cuántos nodos.

  • POD (po): Es el conjunto de contenedores que comparten recursos entre ellos, como el almacenamiento y la red.
  • Replication controllers (rc): Es el servicio que proporciona alta disponibilidad y escalabilidad a los PODs. En caso de incidencia en alguno de los nodos, se encarga de replicar el POD en otro para asegurar que tenemos levantados tantos Pods como hemos definido en la configuración del cluster.
  • Service (svc): Define una IP y puerto únicos que proporciona acceso a un conjunto de PODs. Por defecto, los clientes se conectan a cada uno de los PODs mediante round-robin. Un contenedor no debería poder conectarse a cualquier otra IP dinámica del cluster, sino que la conexión se debe establecer a través del servicio.
Kubernetes Service Delivery Network
Kubernetes – Service Delivery network

El servicio inyecta en cada POD el nombre del servicio y puerto para hacer posible la comunicación con los clientes.

  • Persistent Volumes (pv): Definen áreas de almacenamiento que serán utilizadas por los PODs.
  • Persistent Volume Claim (pvc): Es la función que permite al POD reclamar espacio de disco, a través del PV, y montar un filesystem dentro del POD.
  • Config Maps (cm) and Secrets (sc): Centralizan un conjunto de claves y valores que permiten ser utilizados por otros recursos. Los «secrets» se diferencian de los config maps en que requieren de autorización para poder ser utilizados.
  • Deployment: Es el fichero de configuración, en formato YAML o JSON, para cada uno de los contenedores que forman un POD.

Componentes del Master

El Master es el componente al que todos los nodos del cluster se comunican. Digamos que es el organizador del cluster.

  • etcd: Es el servicio más importante del cluster, ya que almacena toda la información del cluster en formato clave-valor.
  • Kube-ApiServer: Por motivos de seguridad, es el único componente que puede conectarse al servicio etcd mediante el intercambio de claves. Si alguien consiguiera acceder a ApiServer, se haría con el control de todo el cluster.
  • Kube-Controller: Vigila el estado de todos los componentes del cluster.
  • Scheduler: Organiza la distribución de los Pods dentro del cluster.

Componentes de los nodos

  • Kubelet: Es el proceso que corre en cada nodo para comunicarse con el master.
  • CAdvisor: Se encarga de recoger estadísticas de rendimiento de los contenedores (dockers).
  • Kube-proxy: Es una interfaz de red que se utiliza para la comunicación entre los diferentes contenedores.
  • POD: Comentado anteriormente. Es un conjunto de contenedores.

Namespace

Este no es un concepto propio de Kubernetes sino de los contenedores en general.

Un Namespace aísla todos los recursos de un contenedor, como pueden ser: tarjetas de red, puntos de montaje, IDs de los procesos, etc. Solamente los contenedores que pertenezcan al mismo Namespace tienen acceso a los recursos.

Instalación de Kubernetes en Linux CentOS 8 con el repositorio de Docker.com

La instalación de Kubernetes en Linux Centos 8 difiere un poco de Centos 7, pero es muy parecido. No obstante, vamos a ver el proceso completo.

Para este entorno he creado tres servidores virtuales, uno hace de Master y los otros dos son los Workers.

Tareas ejecutadas en los tres nodos del cluster

  • Configuración del fichero /etc/hosts en los tres nodos:
10.0.1.195 kubmaster
10.0.1.46 kubworker1
10.0.1.61 kubworker2
  • Instalación del repositorio de Dockers previo a la instalación de paquetes:
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo

dnf install -y https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.6-3.3.el7.x86_64.rpm

dnf install -y docker-ce
  • Habilitamos el servicio:
systemctl enable docker
systemctl start docker
  • Configuramos el repositorio de yum o dnf de kubernetes:
[[email protected] ~]# cat /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
[[email protected] ~]#
  • Instalamos kubeadm:
dnf install -y kubeadm

systemctl enable kubelet
systemctl start kubelet

Tareas ejecutadas solamente en el nodo Master

  • Deshabilitamos swap, de lo contrario el comando kubeadm init, falla, dando el aviso de que el swap está habilitado:
swapoff -a

Hay que deshabilitarlo también en el /etc/fstab.

  • Generamos la estructura de kubernetes con kubeadm init e iniciamos el servicio del Master. Importante guardarse el token generado al final del proceso.
[[email protected] ~]# kubeadm init
W1201 14:32:20.815754    1151 configset.go:348] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
[init] Using Kubernetes version: v1.19.4
[preflight] Running pre-flight checks
        [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
        [WARNING FileExisting-tc]: tc not found in system path
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [ip-10-0-1-195.eu-west-1.compute.internal kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 10.0.1.195]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [ip-10-0-1-195.eu-west-1.compute.internal localhost] and IPs [10.0.1.195 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [ip-10-0-1-195.eu-west-1.compute.internal localhost] and IPs [10.0.1.195 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 17.003729 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.19" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node ip-10-0-1-195.eu-west-1.compute.internal as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node ip-10-0-1-195.eu-west-1.compute.internal as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: hlsafr.yqsb71wbxp9q3wtt
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 10.0.1.195:6443 --token hlsafr.yqsb71wbxp9q3wtt \
    --discovery-token-ca-cert-hash sha256:f2bfaccf4b0a03546f1d951e9938f062fa0fe3f382920e0a1711afbd126b3754
[[email protected] ~]#

En el caso de que perdamos el comando «join» para añadir un nuevo nodo al cluster de kubernetes, lo podremos recuperar ejecutando el siguiente comando en el nodo master:

[[email protected] ~]# kubeadm token create --print-join-command
kubeadm join 10.0.1.93:6443 --token tqy74a.uvlfzktzomq4f97g --discovery-token-ca-cert-hash sha256:4851759b0ac8472af99853754bc37828a93a250488ebb5fc1e0edb43e40ca8be
[[email protected] ~]#

También podemos invocar el comando añadiendo otros parámetros, como el rango de red:

kubeadm init --apiserver-advertise-address $(hostname -i) --pod-network-cidr 10.244.0.0/16

Seguimos en el nodo Master

kubeadm join --token hlsafr.yqsb71wbxp9q3wtt --discovery-token-ca-cert-hash sha256:f2bfaccf4b0a03546f1d951e9938f062fa0fe3f382920e0a1711afbd126b3754

mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

[[email protected] ~]# kubectl get nodes
NAME                                       STATUS     ROLES    AGE     VERSION
ip-10-0-1-195.eu-west-1.compute.internal   NotReady   master   3m31s   v1.19.4
[[email protected] ~]#

Como vemos, todavía está en estado Not Ready. Todavía tenemos que crear la red de PODs en el Master.

[[email protected] ~]# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/2140ac876ef134e0ed5af15c65e414cf26827915/Documentation/kube-flannel.yml

podsecuritypolicy.policy/psp.flannel.unprivileged created
Warning: rbac.authorization.k8s.io/v1beta1 ClusterRole is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRole
clusterrole.rbac.authorization.k8s.io/flannel configured
Warning: rbac.authorization.k8s.io/v1beta1 ClusterRoleBinding is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRoleBinding
clusterrolebinding.rbac.authorization.k8s.io/flannel unchanged
serviceaccount/flannel unchanged
configmap/kube-flannel-cfg configured
daemonset.apps/kube-flannel-ds-amd64 created
daemonset.apps/kube-flannel-ds-arm64 created
daemonset.apps/kube-flannel-ds-arm created
daemonset.apps/kube-flannel-ds-ppc64le created
daemonset.apps/kube-flannel-ds-s390x created
[[email protected] ~]#

[[email protected] ~]# kubectl get nodes
NAME                                       STATUS   ROLES    AGE   VERSION
ip-10-0-1-195.eu-west-1.compute.internal   Ready    master   6m    v1.19.4
[[email protected] ~]#

Ahora ya vemos el servicio en estado Ready.

  Tutorial Logical Volume Manager (LVM) en Linux

Nos volvemos a conectar a los dos nodos Workers

[[email protected] ~]# kubeadm join 10.0.1.195:6443 --token hlsafr.yqsb71wbxp9q3wtt --discovery-token-ca-cert-hash sha256:f2bfaccf4b0a03546f1d951e9938f062fa0fe3f382920e0a1711afbd126b3754
[preflight] Running pre-flight checks
        [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
        [WARNING FileExisting-tc]: tc not found in system path
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

[[email protected] ~]#


[[email protected] ~]# kubeadm join 10.0.1.195:6443 --token hlsafr.yqsb71wbxp9q3wtt --discovery-token-ca-cert-hash sha256:f2bfaccf4b0a03546f1d951e9938f062fa0fe3f382920e0a1711afbd126b3754
[preflight] Running pre-flight checks
        [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
        [WARNING FileExisting-tc]: tc not found in system path
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

[[email protected] ~]#

Volvemos al nodo Master

[[email protected] ~]# kubectl get nodes
NAME                                       STATUS   ROLES    AGE     VERSION
ip-10-0-1-195.eu-west-1.compute.internal   Ready    master   8m16s   v1.19.4
ip-10-0-1-46.eu-west-1.compute.internal    Ready    <none>   51s     v1.19.4
ip-10-0-1-61.eu-west-1.compute.internal    Ready    <none>   37s     v1.19.4
[[email protected] ~]#

Desplegando WordPress en Kubernetes

No hay mejor manera de ver cómo funciona Kubernetes, que desplegando una aplicación como WordPress con contenedores en un entorno de alta disponibilidad.

Arquitectura del entorno

  • La base de datos MySQL estará ubicada en un POD sin réplica pero con alta disponibilidad.
  • Los servidores WEB Apache los configuremos en otro POD diferente, que se podrán comunicar con la base de datos y tendrán cuatro réplicas.
  • El almacenamiento con alta disponibilidad utilizará cluster de Gluster que ya tenía montado previamente. Para esta prueba y para los que venís de entornos tradicionales, simplemente compartiré un filesystem de Gluster en vez de utilizar un Storage Class, que sería la manera más elegante. Lo dejamos para más adelante.
[[email protected] ~]# df -hP |grep gls
gls1:/glusterwordpress1 1014M  111M  904M  11% /wordpress
gls1:/glustervol1        8.0G  368M  7.7G   5% /mysql
[[email protected] ~]#

Configuración del Deployment

Un deployment es un fichero en JSON o YAML que de define la configuración de cada uno de los pods que van a formar parte de la aplicación que vamos a desplegar. En este caso un POD de MySQL y otro de WordPress, con sus respectivos servicios.

A continuación, vamos a ver su contenido y a explicar cada una de sus secciones:

[[email protected] wordpress]# cat wordpress-dep.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql-wordpress-srv
  labels:
    app: lbl-mysql-wordpress
spec:
  ports:
    - port: 3306
      targetPort: 3306
  selector:
    app: lbl-mysql-wordpress
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-wordpress
  labels:
    app: lbl-mysql-wordpress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lbl-mysql-wordpress
  template:
    metadata:
      labels:
        app: lbl-mysql-wordpress
    spec:
      containers:
      - name: mysql
        image: mysql:latest
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - name: mysql
          containerPort: 3306
          protocol: TCP
        volumeMounts:
        - name: mysql-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-storage
        hostPath:
          path: /mysql
          type: Directory
---
apiVersion: v1
kind: Service
metadata:
  name: http-wordpress-srv
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 31000
  selector:
    app: wordpress
    tier: frontend
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: http-wordpress
  labels:
    app: wordpress
spec:
  replicas: 4
  selector:
    matchLabels:
      app: wordpress
      tier: frontend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
      - image: wordpress:latest
        name: wordpress
        env:
        - name: WORDPRESS_DB_HOST
          value: mysql-wordpress-srv
        - name: WORDPRESS_DB_NAME
          value: "wordpress"
        - name: WORDPRESS_DB_USER
          value: "wordpress"
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - containerPort: 80
          name: wordpress
        volumeMounts:
        - name: wordpress-storage
          mountPath: /var/www/html
      volumes:
      - name: wordpress-storage
        hostPath:
          path: /wordpress
          type: Directory
[[email protected] wordpress]#

También podemos definir un número máximo y mínimo de réplicas:

  minReplicas: 3
  maxReplicas: 10
  • Definición del servicio de MySQL: El servicio vamos a configurar un puerto de escucha y apuntaremos al selector que contiene la base de datos, identificándolo con una etiqueta (label):
apiVersion: v1
kind: Service
metadata:
  name: mysql-wordpress-srv
  labels:
    app: lbl-mysql-wordpress
spec:
  ports:
    - port: 3306
      targetPort: 3306
  selector:
    app: lbl-mysql-wordpress
  type: ClusterIP
  • Configuración del Deployment de MySQL: Este es el POD que contiene la base de datos MySQL. Podemos observar las siguientes características:
    • Está identificado con la etiqueta «lbl-mysql-wordpress», que es la misma que utiliza el servicio configurado anteriormente para que el puerto expuesto apunte a esta base de datos.
    • Solamente tiene configurada una réplica (replicas: 1) para asegurar la integridad de los datos de la base de datos.
    • Descargamos la última imagen del contenedor de MySQL desde Dockerhub, el repositorio por defecto. Lo vemos en la directiva «image: mysql:latest».
    • Configuramos la base de datos para que esuche por el puerto 3306 (containerPort: 3306).
    • Compartimos el filesystem /mysql que tenemos montado en todos los nodos workers en el directorio /var/lib/mysql que hay dentro del POD.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-wordpress
  labels:
    app: lbl-mysql-wordpress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lbl-mysql-wordpress
  template:
    metadata:
      labels:
        app: lbl-mysql-wordpress
    spec:
      containers:
      - name: mysql
        image: mysql:latest
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - name: mysql
          containerPort: 3306
          protocol: TCP
        volumeMounts:
        - name: mysql-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-storage
        hostPath:
          path: /mysql
          type: Directory
  • Configuramos el servicio de Apache:
    • Apunta al puerto 80 del Apache que hay dentro del contenedor pero lo expone externamente al puerto 31000.
    • La etiqueta a utilizar en el selector es «wordpress», ya que se trata del POD que contiene la aplicación WordPress.
apiVersion: v1
kind: Service
metadata:
  name: http-wordpress-srv
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 31000
  selector:
    app: wordpress
    tier: frontend
  type: NodePort
  • Configuración del Deployment de WordPress:
    • Tiene configurada la etiqueta «wordpress», que es a la que apuntaba el selector del servicio configurado anteriormente.
    • Este POD tiene 4 réplicas, lo que significa que levantaremos 4 servidores WEB (replicas: 4).
    • Descargamos la última imagen del contenedor de WordPress (image: wordpress:latest).
    • Configuramos algunas variables de entorno de WordPress que están integradas en el fichero «wp-config.php» del propio WordPress:
      • WORDPRESS_DB_HOST: Es el servidor donde corre la base de datos. En nuestro caso, apuntamos al nombre que tiene servicio de MySQL que hemos configurado anteriormente.
      • WORDPRESS_DB_NAME: Es el nombre de la base de datos de WordPress que vamos a crear antes de desplegar la aplicación. Lo mismo ocuure coon el usuario «WORDPRESS_DB_USER» y la contraseña «WORDPRESS_DB_PASSWORD» de la base de datos.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: http-wordpress
  labels:
    app: wordpress
spec:
  replicas: 4
  selector:
    matchLabels:
      app: wordpress
      tier: frontend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
      - image: wordpress:latest
        name: wordpress
        env:
        - name: WORDPRESS_DB_HOST
          value: mysql-wordpress-srv
        - name: WORDPRESS_DB_NAME
          value: "wordpress"
        - name: WORDPRESS_DB_USER
          value: "wordpress"
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - containerPort: 80
          name: wordpress
        volumeMounts:
        - name: wordpress-storage
          mountPath: /var/www/html
      volumes:
      - name: wordpress-storage
        hostPath:
          path: /wordpress
          type: Directory

Antes de desplegar toda la aplicación, vamos a configurar la clave secreta del usuario «root» de WordPress con un nuevo fichero YAML:

[[email protected] wordpress]$ echo -n '[email protected]' | base64
UzNjcjN0UEBzc3cwcmQ=
[[email protected] wordpress]$

[[email protected] wordpress]$ cat mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-pass
type: Opaque
data:
  password: UzNjcjN0UEBzc3cwcmQ=
[[email protected] wordpress]$

[[email protected] wordpress]$ kubectl create -f mysql-secret.yaml
secret/mysql-pass created
[[email protected] wordpress]$

[[email protected] wordpress]# kubectl get secret
NAME                  TYPE                                  DATA   AGE
default-token-zdt2n   kubernetes.io/service-account-token   3      2d9h
mysql-pass            Opaque                                1      2d9h
[[email protected] wordpress]#

Espero que se haya entendido el funcionamiento de todo el despliguede la aplicación de WordPress y la base de datos. Ahora vamos a aplicar la configuración.

[[email protected] wordpress]# kubectl apply -f wordpress-dep.yaml
service/mysql-wordpress-srv unchanged
deployment.apps/mysql-wordpress unchanged
service/http-wordpress-srv unchanged
deployment.apps/http-wordpress unchanged
[[email protected] wordpress]#

[[email protected] wordpress]# kubectl get services
NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
http-wordpress-srv    NodePort    10.108.247.99   <none>        80:31000/TCP   15h
kubernetes            ClusterIP   10.96.0.1       <none>        443/TCP        2d9h
mysql-wordpress-srv   ClusterIP   10.107.71.168   <none>        3306/TCP       15h
ago)   15h
[[email protected] wordpress]#

[[email protected] wordpress]# kubectl get pods --show-labels
NAME                               READY   STATUS    RESTARTS      AGE   LABELS
centos                             1/1     Running   2 (13h ago)   14h   run=centos
http-wordpress-6f555694df-fjkrk    1/1     Running   1 (13h ago)   15h   app=wordpress,pod-template-hash=6f555694df,tier=frontend
http-wordpress-6f555694df-kfjt7    1/1     Running   1 (13h ago)   15h   app=wordpress,pod-template-hash=6f555694df,tier=frontend
http-wordpress-6f555694df-vn9tg    1/1     Running   1 (13h ago)   15h   app=wordpress,pod-template-hash=6f555694df,tier=frontend
http-wordpress-6f555694df-zvrgr    1/1     Running   1 (13h ago)   15h   app=wordpress,pod-template-hash=6f555694df,tier=frontend
mysql-wordpress-574544844c-zjfhs   1/1     Running   1 (13h ago)   15h   app=lbl-mysql-wordpress,pod-template-hash=574544844c
[[email protected] wordpress]#

Una vez desplegados los contenedores y servicios vamos a conectarnos al de MySQL para crear el usuario «wordpress» y su contraseña, que habíamos configurado en las variables de entorno del contenedor.

[[email protected] wordpress]# kubectl exec -ti mysql-wordpress-574544844c-zjfhs -- /bin/sh
#

# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.27 MySQL Community Server - GPL

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> CREATE USER [email protected]'%' IDENTIFIED BY '[email protected]';
Query OK, 0 rows affected (0.03 sec)

mysql> GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'%';
Query OK, 0 rows affected (0.01 sec)

mysql>


mysql>

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| wordpress          |
+--------------------+
5 rows in set (0.00 sec)

mysql>

Respecto a las variables de entorno configuradas en el fichero YAML, podemos comprobar que se han configurado correctamente en el POD:

# set |grep WORDPRESS
HTTP_WORDPRESS_SRV_PORT='tcp://10.108.247.99:80'
HTTP_WORDPRESS_SRV_PORT_80_TCP='tcp://10.108.247.99:80'
HTTP_WORDPRESS_SRV_PORT_80_TCP_ADDR='10.108.247.99'
HTTP_WORDPRESS_SRV_PORT_80_TCP_PORT='80'
HTTP_WORDPRESS_SRV_PORT_80_TCP_PROTO='tcp'
HTTP_WORDPRESS_SRV_SERVICE_HOST='10.108.247.99'
HTTP_WORDPRESS_SRV_SERVICE_PORT='80'
MYSQL_WORDPRESS_SRV_PORT='tcp://10.107.71.168:3306'
MYSQL_WORDPRESS_SRV_PORT_3306_TCP='tcp://10.107.71.168:3306'
MYSQL_WORDPRESS_SRV_PORT_3306_TCP_ADDR='10.107.71.168'
MYSQL_WORDPRESS_SRV_PORT_3306_TCP_PORT='3306'
MYSQL_WORDPRESS_SRV_PORT_3306_TCP_PROTO='tcp'
MYSQL_WORDPRESS_SRV_SERVICE_HOST='10.107.71.168'
MYSQL_WORDPRESS_SRV_SERVICE_PORT='3306'
#

Finalmente, comprobamos que la aplicación se ha desplegado correctamente si nos conectamos a su URL pública:

Despliegue de WordPress en Kubernetes

Podemos observar que el puerto de escucha es el 31000, que es el que hemos configurado en el fichero YAML.

Uso de las clases de almacenamiento en Kubernetes (STORAGE CLASS)

En la página oficial de Kubernetes, podemos observar que existen multitud de clases a utilizar (NFS, Ceph, Gluster, Amazon AWS, Azure, etc.): https://kubernetes.io/docs/concepts/storage/storage-classes/

Como ya tengo el cluster de Gluster montado, voy a utilizar esta.

Configurando la clase de almacenamiento GLUSTER con Kubernetes

En el deploy anterior hemos configurado el almacenamiento importando un filesystem que ya estaba montado previamente en el sistema pero lo que vamos a hacer a continuación es apuntar desde dentro del POD al servicio de Gluster sin que haga falta importar ningún filesystem del Host. De hecho, ni lo vamos a montar.

Primero vamos a definir un Endpoint dentro del cluster de Kubernetes para hacer visible el cluster de Gluster:

[[email protected] wordpress]# cat gluster-endpoint.yml
apiVersion: v1
kind: Endpoints
metadata:
  name: glusterfs-cluster
  labels:
    storage.k8s.io/name: glusterfs
subsets:
  - addresses:
      - ip: 10.0.1.65
        hostname: gls1
      - ip: 10.0.1.120
        hostname: gls2
      - ip: 10.0.1.13
        hostname: gls3
    ports:
      - port: 1
[[email protected] wordpress]#

[[email protected] wordpress]# kubectl apply -f gluster-endpoint.yml
endpoints/glusterfs-cluster created
[[email protected] wordpress]#

[[email protected] wordpress]# kubectl get endpoints --show-labels |egrep "NAME|glusterfs"
NAME                  ENDPOINTS                                                  AGE     LABELS
glusterfs-cluster     10.0.1.65:1,10.0.1.120:1,10.0.1.13:1                       55s     storage.k8s.io/name=glusterfs
[[email protected] wordpress]#

Comentar que, previamente, he creado un volumen nuevo en Gluster llamado «glustervol2»:

[[email protected] ~]# vgcreate vggluster /dev/xvdg
  Physical volume "/dev/xvdg" successfully created.
  Volume group "vggluster" successfully created
[[email protected] ~]#

[[email protected] ~]# lvcreate -n lvgluster -l+100%FREE vggluster
  Logical volume "lvgluster" created.
[[email protected] ~]#

[[email protected] ~]# mkfs.xfs /dev/vggluster/lvgluster
meta-data=/dev/vggluster/lvgluster isize=512    agcount=4, agsize=327424 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=1, rmapbt=0
         =                       reflink=1
data     =                       bsize=4096   blocks=1309696, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
log      =internal log           bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
[[email protected] ~]#

[[email protected] ~]# mkdir /gluster
[[email protected] ~]# vi /etc/fstab
[[email protected] ~]# tail -1 /etc/fstab
/dev/vggluster/lvgluster        /gluster        xfs     defaults        0 0
[[email protected] ~]# mount /gluster/
[[email protected] ~]# mkdir /gluster/brick
[[email protected] ~]# df -hP /gluster/
Filesystem                       Size  Used Avail Use% Mounted on
/dev/mapper/vggluster-lvgluster  5.0G   68M  5.0G   2% /gluster
[[email protected] ~]#


[[email protected] ~]# gluster volume create glustervol2 replica 3 transport tcp gls1:/gluster/brick gls2:/gluster/brick gls3:/gluster/brick
volume create: glustervol2: success: please start the volume to access data
[[email protected] ~]# gluster volume start glustervol2
volume start: glustervol2: success
[[email protected] ~]#


Volume Name: glustervol2
Type: Replicate
Volume ID: 300d2b27-de42-454a-b1e2-791ad2c0bdc6
Status: Started
Snapshot Count: 0
Number of Bricks: 1 x 3 = 3
Transport-type: tcp
Bricks:
Brick1: gls1:/gluster/brick
Brick2: gls2:/gluster/brick
Brick3: gls3:/gluster/brick
Options Reconfigured:
cluster.granular-entry-heal: on
storage.fips-mode-rchecksum: on
transport.address-family: inet
nfs.disable: on
performance.client-io-threads: off

Nota: Un volume group forma parte de la arquitectura LVM.

Una vez que ya hemos descubierto el cluster de Gluster, toca modificar el POD para utilizar el servicio de Gluster en lugar de importar un filesystem del Host. Hacerlo es muy sencillo:

  Administración de usuarios y grupos en Linux

En la sección de volúmenes del POD, tenemos que apuntar al nombre del servicio de Gluster que hemos asignado en la configuración del Endpoint (glusterfs-cluster) y al volúmen que nos interesa (glustervol2):


 volumes:
      - name: mysql-storage
        glusterfs:
          endpoints: glusterfs-cluster
          path: glustervol2
          readOnly: no

Una vez aplicamos la nueva configuración del Deployment, podremos entrar en el POD y comprobar que el filesystem ha montado correctamente:

[[email protected] wordpress]# kubectl exec -ti mysql-wordpress-6c655fdc6d-jrh6c -- /bin/sh
# df -hP |grep gluster
10.0.1.13:glustervol2  5.0G  309M  4.7G   7% /var/lib/mysql
#

Configurando la clase de almacenamiento en bloque de CEPH con Kubernetes

Ceph es el método de almacenamiento con réplica más utilizado actualmente con Kubernetes, así que vamos a crear un pool de Ceph que lo vamos a utilizar como almacenamiento en bloque.

Echa un vistazo al tutorial de Ceph.

Para ello, vamos a modificar un poquito la guía oficial https://docs.ceph.com/en/latest/rbd/rbd-kubernetes/

  • Creamos el pool de Ceph desde uno de los servidores de monitorización de Ceph:
[[email protected] ~]# ceph osd pool create wordpress
pool 'wordpress' already exists
[[email protected] ~]# rbd pool init wordpress
[[email protected] ~]#

Como podemos observar, en este caso ya tenía creado el pool de una prueba anterior.

[[email protected] ~]# ceph df
--- RAW STORAGE ---
CLASS    SIZE   AVAIL     USED  RAW USED  %RAW USED
ssd    15 GiB  15 GiB  160 MiB   160 MiB       1.05
TOTAL  15 GiB  15 GiB  160 MiB   160 MiB       1.05

--- POOLS ---
POOL                   ID  PGS  STORED  OBJECTS     USED  %USED  MAX AVAIL
wordpress               3   32  33 MiB       41  100 MiB   0.69    4.7 GiB
device_health_metrics   4   32     0 B        0      0 B      0    4.7 GiB
[[email protected] ~]#

Crear un pool en Ceph

  • Creamos el usuario «wordpress» que se va a conectar al pool de Ceph llamado «wordpress» (el que acabamos de crear):
[[email protected] ~]# ceph auth get-or-create client.wordpress mon 'profile rbd' osd 'profile rbd pool=wordpress' mgr 'profile rbd pool=wordpress'
[client.wordpress]
        key = AQB+uMBhuEAUOxAAnB5IHvnDb2WyA4CH3Zn2LA==
[[email protected] ~]#
  • Obtenemos cierta información del cluster de Ceph que vamos a necesitar para configurar el servicio de Kubernetes para que se pueda utilizar el almacenamiento de Ceph:
[[email protected] ~]# ceph mon dump
epoch 9
fsid 9ebd1a74-5a9a-11ec-97be-064744202561
last_changed 2021-12-12T14:40:45.317788+0000
created 2021-12-11T15:56:06.251343+0000
min_mon_release 16 (pacific)
election_strategy: 1
0: [v2:10.0.1.212:3300/0,v1:10.0.1.212:6789/0] mon.cephmon3
1: [v2:10.0.1.182:3300/0,v1:10.0.1.182:6789/0] mon.cephosd3
2: [v2:10.0.1.235:3300/0,v1:10.0.1.235:6789/0] mon.cephmon4
3: [v2:10.0.1.64:3300/0,v1:10.0.1.64:6789/0] mon.cephosd1
4: [v2:10.0.1.204:3300/0,v1:10.0.1.204:6789/0] mon.cephosd2
dumped monmap epoch 9
[[email protected] ~]#
  • Configuramos el CSI Map, donde identificamos las IPs de los monitores de Ceph y el ID del cluster de Ceph. Vamos a utilizar todo el rato el namespace «ns-wordpress»:
[[email protected] wpceph2]# cat csi-config-map.yaml
---
apiVersion: v1
kind: ConfigMap
data:
  config.json: |-
    [
      {
        "clusterID": "9ebd1a74-5a9a-11ec-97be-064744202561",
        "monitors": [
          "10.0.1.212:6789",
          "10.0.1.235:6789"
        ]
      }
    ]
metadata:
  name: ceph-csi-config
  namespace: ns-wordpress
[[email protected] wpceph2]#


[[email protected] wpceph2]# kubectl apply -f csi-config-map.yaml
configmap/ceph-csi-config created
[[email protected] wpceph2]#
  • Las versiones recientes de ceph-csi también requieren un objeto ConfigMap adicional para definir los detalles del proveedor del Servicio de administración de claves (KMS):

[[email protected] wpceph2]# cat csi-kms-config-map.yaml
---
apiVersion: v1
kind: ConfigMap
data:
  config.json: |-
    {}
metadata:
  name: ceph-csi-encryption-kms-config
  namespace: ns-wordpress
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f csi-kms-config-map.yaml
configmap/ceph-csi-encryption-kms-config created
[[email protected] wpceph2]#
  • Añadimos la configuración requerida para el fichero ceph.conf que hay dentro de los contenedores:
[[email protected] wpceph2]# cat ceph-config-map.yaml
---
apiVersion: v1
kind: ConfigMap
data:
  ceph.conf: |
    [global]
    auth_cluster_required = cephx
    auth_service_required = cephx
    auth_client_required = cephx
  # keyring is a required key and its value should be empty
  keyring: |
metadata:
  name: ceph-config
  namespace: ns-wordpress
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f ceph-config-map.yaml
configmap/ceph-config created
[[email protected] wpceph2]#
  • Generamos el fichero de credenciales correspondiente para conectarnos al cluster de Ceph con el usuario «wordpress»:
[[email protected] wpceph2]# cat csi-rbd-secret.yaml
---
apiVersion: v1
kind: Secret
metadata:
  name: csi-rbd-secret
  namespace: ns-wordpress
stringData:
  userID: wordpress
  userKey: AQB+uMBhuEAUOxAAnB5IHvnDb2WyA4CH3Zn2LA==
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f csi-rbd-secret.yaml
secret/csi-rbd-secret created
[[email protected] wpceph2]#
  • Creamos la cuenta de servicio y el plugin de conexión al recurso RBD de Ceph:
[[email protected] wpceph2]# wget https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-provisioner-rbac.yaml
--2021-12-21 05:55:12--  https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-provisioner-rbac.yaml
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.110.133, 185.199.111.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3116 (3.0K) [text/plain]
Saving to: ‘csi-provisioner-rbac.yaml’

100%[===================================================================================================================================================>] 3,116       --.-K/s   in 0s

2021-12-21 05:55:13 (41.6 MB/s) - ‘csi-provisioner-rbac.yaml’ saved [3116/3116]

[[email protected] wpceph2]# 

Tenemos que sustituir el namespace «default» por el que necesitemos. Para este ejemplo, usamos el «ns-wordpress»:

[[email protected] wpceph2]# cat csi-provisioner-rbac.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: rbd-csi-provisioner
  # replace with non-default namespace name
  namespace: ns-wordpress

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: rbd-external-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "update", "delete", "patch"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims/status"]
    verbs: ["update", "patch"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["snapshot.storage.k8s.io"]
    resources: ["volumesnapshots"]
    verbs: ["get", "list"]
  - apiGroups: ["snapshot.storage.k8s.io"]
    resources: ["volumesnapshotcontents"]
    verbs: ["create", "get", "list", "watch", "update", "delete"]
  - apiGroups: ["snapshot.storage.k8s.io"]
    resources: ["volumesnapshotclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["volumeattachments"]
    verbs: ["get", "list", "watch", "update", "patch"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["volumeattachments/status"]
    verbs: ["patch"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["csinodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["snapshot.storage.k8s.io"]
    resources: ["volumesnapshotcontents/status"]
    verbs: ["update"]
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get"]
  - apiGroups: [""]
    resources: ["serviceaccounts"]
    verbs: ["get"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: rbd-csi-provisioner-role
subjects:
  - kind: ServiceAccount
    name: rbd-csi-provisioner
    # replace with non-default namespace name
    namespace: ns-wordpress
roleRef:
  kind: ClusterRole
  name: rbd-external-provisioner-runner
  apiGroup: rbac.authorization.k8s.io

---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  # replace with non-default namespace name
  namespace: ns-wordpress
  name: rbd-external-provisioner-cfg
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch", "create", "update", "delete"]
  - apiGroups: ["coordination.k8s.io"]
    resources: ["leases"]
    verbs: ["get", "watch", "list", "delete", "update", "create"]

---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: rbd-csi-provisioner-role-cfg
  # replace with non-default namespace name
  namespace: ns-wordpress
subjects:
  - kind: ServiceAccount
    name: rbd-csi-provisioner
    # replace with non-default namespace name
    namespace: ns-wordpress
roleRef:
  kind: Role
  name: rbd-external-provisioner-cfg
  apiGroup: rbac.authorization.k8s.io
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f csi-provisioner-rbac.yaml
serviceaccount/rbd-csi-provisioner created
clusterrole.rbac.authorization.k8s.io/rbd-external-provisioner-runner unchanged
clusterrolebinding.rbac.authorization.k8s.io/rbd-csi-provisioner-role configured
role.rbac.authorization.k8s.io/rbd-external-provisioner-cfg created
rolebinding.rbac.authorization.k8s.io/rbd-csi-provisioner-role-cfg created
[[email protected] wpceph2]#

[[email protected] wpceph2]# wget https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-nodeplugin-rbac.yaml
--2021-12-21 05:58:24--  https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-nodeplugin-rbac.yaml
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1110 (1.1K) [text/plain]
Saving to: ‘csi-nodeplugin-rbac.yaml’

100%[===================================================================================================================================================>] 1,110       --.-K/s   in 0s

2021-12-21 05:58:24 (60.9 MB/s) - ‘csi-nodeplugin-rbac.yaml’ saved [1110/1110]

[[email protected] wpceph2]#

[[email protected] wpceph2]# cat csi-nodeplugin-rbac.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: rbd-csi-nodeplugin
  # replace with non-default namespace name
  namespace: ns-wordpress
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: rbd-csi-nodeplugin
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get"]
  # allow to read Vault Token and connection options from the Tenants namespace
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get"]
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get"]
  - apiGroups: [""]
    resources: ["serviceaccounts"]
    verbs: ["get"]
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["volumeattachments"]
    verbs: ["list", "get"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: rbd-csi-nodeplugin
subjects:
  - kind: ServiceAccount
    name: rbd-csi-nodeplugin
    # replace with non-default namespace name
    namespace: ns-wordpress
roleRef:
  kind: ClusterRole
  name: rbd-csi-nodeplugin
  apiGroup: rbac.authorization.k8s.io
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f csi-nodeplugin-rbac.yaml
serviceaccount/rbd-csi-nodeplugin created
clusterrole.rbac.authorization.k8s.io/rbd-csi-nodeplugin unchanged
clusterrolebinding.rbac.authorization.k8s.io/rbd-csi-nodeplugin configured
[[email protected] wpceph2]#
  • A continuación, creamos el servicio de aprovisionamiento de disco:
[[email protected] wpceph2]# wget https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-rbdplugin-provisioner.yaml
--2021-12-21 06:01:38--  https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-rbdplugin-provisioner.yaml
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7294 (7.1K) [text/plain]
Saving to: ‘csi-rbdplugin-provisioner.yaml.2’

100%[===================================================================================================================================================>] 7,294       --.-K/s   in 0s

2021-12-21 06:01:39 (61.1 MB/s) - ‘csi-rbdplugin-provisioner.yaml.2’ saved [7294/7294]

[[email protected] wpceph2]#

Cambiamos el nombre del namespace:

[[email protected] wpceph2]# cat csi-rbdplugin-provisioner.yaml
---
kind: Service
apiVersion: v1
metadata:
  name: csi-rbdplugin-provisioner
  # replace with non-default namespace name
  namespace: ns-wordpress
  labels:
    app: csi-metrics
spec:
  selector:
    app: csi-rbdplugin-provisioner
  ports:
    - name: http-metrics
      port: 8080
      protocol: TCP
      targetPort: 8680

---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: csi-rbdplugin-provisioner
  # replace with non-default namespace name
  namespace: ns-wordpress
spec:
  replicas: 3
  selector:
    matchLabels:
      app: csi-rbdplugin-provisioner
  template:
    metadata:
      labels:
        app: csi-rbdplugin-provisioner
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - csi-rbdplugin-provisioner
              topologyKey: "kubernetes.io/hostname"
      serviceAccountName: rbd-csi-provisioner
      priorityClassName: system-cluster-critical
      containers:
        - name: csi-provisioner
          image: k8s.gcr.io/sig-storage/csi-provisioner:v3.0.0
          args:
            - "--csi-address=$(ADDRESS)"
            - "--v=5"
            - "--timeout=150s"
            - "--retry-interval-start=500ms"
            - "--leader-election=true"
            #  set it to true to use topology based provisioning
            - "--feature-gates=Topology=false"
            # if fstype is not specified in storageclass, ext4 is default
            - "--default-fstype=ext4"
            - "--extra-create-metadata=true"
          env:
            - name: ADDRESS
              value: unix:///csi/csi-provisioner.sock
          imagePullPolicy: "IfNotPresent"
          volumeMounts:
            - name: socket-dir
              mountPath: /csi
        - name: csi-snapshotter
          image: k8s.gcr.io/sig-storage/csi-snapshotter:v4.2.0
          args:
            - "--csi-address=$(ADDRESS)"
            - "--v=5"
            - "--timeout=150s"
            - "--leader-election=true"
          env:
            - name: ADDRESS
              value: unix:///csi/csi-provisioner.sock
          imagePullPolicy: "IfNotPresent"
          volumeMounts:
            - name: socket-dir
              mountPath: /csi
        - name: csi-attacher
          image: k8s.gcr.io/sig-storage/csi-attacher:v3.3.0
          args:
            - "--v=5"
            - "--csi-address=$(ADDRESS)"
            - "--leader-election=true"
            - "--retry-interval-start=500ms"
          env:
            - name: ADDRESS
              value: /csi/csi-provisioner.sock
          imagePullPolicy: "IfNotPresent"
          volumeMounts:
            - name: socket-dir
              mountPath: /csi
        - name: csi-resizer
          image: k8s.gcr.io/sig-storage/csi-resizer:v1.3.0
          args:
            - "--csi-address=$(ADDRESS)"
            - "--v=5"
            - "--timeout=150s"
            - "--leader-election"
            - "--retry-interval-start=500ms"
            - "--handle-volume-inuse-error=false"
          env:
            - name: ADDRESS
              value: unix:///csi/csi-provisioner.sock
          imagePullPolicy: "IfNotPresent"
          volumeMounts:
            - name: socket-dir
              mountPath: /csi
        - name: csi-rbdplugin
          # for stable functionality replace canary with latest release version
          image: quay.io/cephcsi/cephcsi:canary
          args:
            - "--nodeid=$(NODE_ID)"
            - "--type=rbd"
            - "--controllerserver=true"
            - "--endpoint=$(CSI_ENDPOINT)"
            - "--v=5"
            - "--drivername=rbd.csi.ceph.com"
            - "--pidlimit=-1"
            - "--rbdhardmaxclonedepth=8"
            - "--rbdsoftmaxclonedepth=4"
            - "--enableprofiling=false"
          env:
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: NODE_ID
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            # - name: KMS_CONFIGMAP_NAME
            #   value: encryptionConfig
            - name: CSI_ENDPOINT
              value: unix:///csi/csi-provisioner.sock
          imagePullPolicy: "IfNotPresent"
          volumeMounts:
            - name: socket-dir
              mountPath: /csi
            - mountPath: /dev
              name: host-dev
            - mountPath: /sys
              name: host-sys
            - mountPath: /lib/modules
              name: lib-modules
              readOnly: true
            - name: ceph-csi-config
              mountPath: /etc/ceph-csi-config/
            - name: ceph-csi-encryption-kms-config
              mountPath: /etc/ceph-csi-encryption-kms-config/
            - name: keys-tmp-dir
              mountPath: /tmp/csi/keys
            - name: ceph-config
              mountPath: /etc/ceph/
        - name: csi-rbdplugin-controller
          # for stable functionality replace canary with latest release version
          image: quay.io/cephcsi/cephcsi:canary
          args:
            - "--type=controller"
            - "--v=5"
            - "--drivername=rbd.csi.ceph.com"
            - "--drivernamespace=$(DRIVER_NAMESPACE)"
          env:
            - name: DRIVER_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          imagePullPolicy: "IfNotPresent"
          volumeMounts:
            - name: ceph-csi-config
              mountPath: /etc/ceph-csi-config/
            - name: keys-tmp-dir
              mountPath: /tmp/csi/keys
            - name: ceph-config
              mountPath: /etc/ceph/
        - name: liveness-prometheus
          image: quay.io/cephcsi/cephcsi:canary
          args:
            - "--type=liveness"
            - "--endpoint=$(CSI_ENDPOINT)"
            - "--metricsport=8680"
            - "--metricspath=/metrics"
            - "--polltime=60s"
            - "--timeout=3s"
          env:
            - name: CSI_ENDPOINT
              value: unix:///csi/csi-provisioner.sock
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
          volumeMounts:
            - name: socket-dir
              mountPath: /csi
          imagePullPolicy: "IfNotPresent"
      volumes:
        - name: host-dev
          hostPath:
            path: /dev
        - name: host-sys
          hostPath:
            path: /sys
        - name: lib-modules
          hostPath:
            path: /lib/modules
        - name: socket-dir
          emptyDir: {
            medium: "Memory"
          }
        - name: ceph-config
          configMap:
            name: ceph-config
        - name: ceph-csi-config
          configMap:
            name: ceph-csi-config
        - name: ceph-csi-encryption-kms-config
          configMap:
            name: ceph-csi-encryption-kms-config
        - name: keys-tmp-dir
          emptyDir: {
            medium: "Memory"
          }
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f csi-rbdplugin-provisioner.yaml
service/csi-rbdplugin-provisioner unchanged
deployment.apps/csi-rbdplugin-provisioner unchanged
[[email protected] wpceph2]#
[[email protected] wpceph2]# wget https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-rbdplugin.yaml
--2021-12-21 06:03:34--  https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-rbdplugin.yaml
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.111.133, 185.199.108.133, 185.199.109.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.111.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6677 (6.5K) [text/plain]
Saving to: ‘csi-rbdplugin.yaml.2’

100%[===================================================================================================================================================>] 6,677       --.-K/s   in 0s

2021-12-21 06:03:34 (57.1 MB/s) - ‘csi-rbdplugin.yaml.2’ saved [6677/6677]

[[email protected] wpceph2]#

Cambiamos el nombre del namespace:

[[email protected] wpceph2]# cat csi-rbdplugin.yaml
---
kind: DaemonSet
apiVersion: apps/v1
metadata:
  name: csi-rbdplugin
  # replace with non-default namespace name
  namespace: ns-wordpress
spec:
  selector:
    matchLabels:
      app: csi-rbdplugin
  template:
    metadata:
      labels:
        app: csi-rbdplugin
    spec:
      serviceAccountName: rbd-csi-nodeplugin
      hostNetwork: true
      hostPID: true
      priorityClassName: system-node-critical
      # to use e.g. Rook orchestrated cluster, and mons' FQDN is
      # resolved through k8s service, set dns policy to cluster first
      dnsPolicy: ClusterFirstWithHostNet
      containers:
        - name: driver-registrar
          # This is necessary only for systems with SELinux, where
          # non-privileged sidecar containers cannot access unix domain socket
          # created by privileged CSI driver container.
          securityContext:
            privileged: true
          image: k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.3.0
          args:
            - "--v=5"
            - "--csi-address=/csi/csi.sock"
            - "--kubelet-registration-path=/var/lib/kubelet/plugins/rbd.csi.ceph.com/csi.sock"
          env:
            - name: KUBE_NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
          volumeMounts:
            - name: socket-dir
              mountPath: /csi
            - name: registration-dir
              mountPath: /registration
        - name: csi-rbdplugin
          securityContext:
            privileged: true
            capabilities:
              add: ["SYS_ADMIN"]
            allowPrivilegeEscalation: true
          # for stable functionality replace canary with latest release version
          image: quay.io/cephcsi/cephcsi:canary
          args:
            - "--nodeid=$(NODE_ID)"
            - "--pluginpath=/var/lib/kubelet/plugins"
            - "--stagingpath=/var/lib/kubelet/plugins/kubernetes.io/csi/pv/"
            - "--type=rbd"
            - "--nodeserver=true"
            - "--endpoint=$(CSI_ENDPOINT)"
            - "--v=5"
            - "--drivername=rbd.csi.ceph.com"
            - "--enableprofiling=false"
            # If topology based provisioning is desired, configure required
            # node labels representing the nodes topology domain
            # and pass the label names below, for CSI to consume and advertise
            # its equivalent topology domain
            # - "--domainlabels=failure-domain/region,failure-domain/zone"
          env:
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: NODE_ID
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            # - name: KMS_CONFIGMAP_NAME
            #   value: encryptionConfig
            - name: CSI_ENDPOINT
              value: unix:///csi/csi.sock
          imagePullPolicy: "IfNotPresent"
          volumeMounts:
            - name: socket-dir
              mountPath: /csi
            - mountPath: /dev
              name: host-dev
            - mountPath: /sys
              name: host-sys
            - mountPath: /run/mount
              name: host-mount
            - mountPath: /etc/selinux
              name: etc-selinux
              readOnly: true
            - mountPath: /lib/modules
              name: lib-modules
              readOnly: true
            - name: ceph-csi-config
              mountPath: /etc/ceph-csi-config/
            - name: ceph-csi-encryption-kms-config
              mountPath: /etc/ceph-csi-encryption-kms-config/
            - name: plugin-dir
              mountPath: /var/lib/kubelet/plugins
              mountPropagation: "Bidirectional"
            - name: mountpoint-dir
              mountPath: /var/lib/kubelet/pods
              mountPropagation: "Bidirectional"
            - name: keys-tmp-dir
              mountPath: /tmp/csi/keys
            - name: ceph-logdir
              mountPath: /var/log/ceph
            - name: ceph-config
              mountPath: /etc/ceph/
        - name: liveness-prometheus
          securityContext:
            privileged: true
          image: quay.io/cephcsi/cephcsi:canary
          args:
            - "--type=liveness"
            - "--endpoint=$(CSI_ENDPOINT)"
            - "--metricsport=8680"
            - "--metricspath=/metrics"
            - "--polltime=60s"
            - "--timeout=3s"
          env:
            - name: CSI_ENDPOINT
              value: unix:///csi/csi.sock
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
          volumeMounts:
            - name: socket-dir
              mountPath: /csi
          imagePullPolicy: "IfNotPresent"
      volumes:
        - name: socket-dir
          hostPath:
            path: /var/lib/kubelet/plugins/rbd.csi.ceph.com
            type: DirectoryOrCreate
        - name: plugin-dir
          hostPath:
            path: /var/lib/kubelet/plugins
            type: Directory
        - name: mountpoint-dir
          hostPath:
            path: /var/lib/kubelet/pods
            type: DirectoryOrCreate
        - name: ceph-logdir
          hostPath:
            path: /var/log/ceph
            type: DirectoryOrCreate
        - name: registration-dir
          hostPath:
            path: /var/lib/kubelet/plugins_registry/
            type: Directory
        - name: host-dev
          hostPath:
            path: /dev
        - name: host-sys
          hostPath:
            path: /sys
        - name: etc-selinux
          hostPath:
            path: /etc/selinux
        - name: host-mount
          hostPath:
            path: /run/mount
        - name: lib-modules
          hostPath:
            path: /lib/modules
        - name: ceph-config
          configMap:
            name: ceph-config
        - name: ceph-csi-config
          configMap:
            name: ceph-csi-config
        - name: ceph-csi-encryption-kms-config
          configMap:
            name: ceph-csi-encryption-kms-config
        - name: keys-tmp-dir
          emptyDir: {
            medium: "Memory"
          }
---
# This is a service to expose the liveness metrics
apiVersion: v1
kind: Service
metadata:
  name: csi-metrics-rbdplugin
  # replace with non-default namespace name
  namespace: ns-wordpress
  labels:
    app: csi-metrics
spec:
  ports:
    - name: http-metrics
      port: 8080
      protocol: TCP
      targetPort: 8680
  selector:
    app: csi-rbdplugin
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f csi-rbdplugin.yaml
daemonset.apps/csi-rbdplugin unchanged
service/csi-metrics-rbdplugin created
[[email protected] wpceph2]#
  • Creamos el StorageClass de Ceph RBD apuntando al pool de Ceph «wordpress»:
[[email protected] wpceph2]# cat csi-rbd-sc.yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: csi-rbd-sc
   namespace: ns-wordpress
provisioner: rbd.csi.ceph.com
parameters:
   clusterID: 9ebd1a74-5a9a-11ec-97be-064744202561
   pool: wordpress
   imageFeatures: layering
   csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret
   csi.storage.k8s.io/provisioner-secret-namespace: default
   csi.storage.k8s.io/controller-expand-secret-name: csi-rbd-secret
   csi.storage.k8s.io/controller-expand-secret-namespace: default
   csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret
   csi.storage.k8s.io/node-stage-secret-namespace: default
reclaimPolicy: Delete
allowVolumeExpansion: true
mountOptions:
   - discard
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f csi-rbd-sc.yaml
storageclass.storage.k8s.io/csi-rbd-sc created
[[email protected] wpceph2]#
  • Reclamamos 1GB del espacio de disco:
[[email protected] wpceph2]# cat raw-block-pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: raw-block-pvc
  namespace: ns-wordpress
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Block
  resources:
    requests:
      storage: 1Gi
  storageClassName: csi-rbd-sc
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f raw-block-pvc.yaml
persistentvolumeclaim/raw-block-pvc created
[[email protected] wpceph2]#
  • Creamos un POD de pruebas que utiliza el espacio de disco reclamado anteriormente:
[[email protected] wpceph2]# cat raw-block-pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-raw-block-volume
  namespace: ns-wordpress
spec:
  containers:
    - name: fc-container
      image: fedora:26
      command: ["/bin/sh", "-c"]
      args: ["tail -f /dev/null"]
      volumeDevices:
        - name: data
          devicePath: /dev/xvda
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: raw-block-pvc
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f raw-block-pod.yaml
pod/pod-with-raw-block-volume created
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl get pods -n ns-wordpress
NAME                                        READY   STATUS    RESTARTS      AGE
csi-rbdplugin-458zw                         3/3     Running   0             6m59s
csi-rbdplugin-provisioner-8cc8d794f-cxbtm   7/7     Running   0             6m59s
csi-rbdplugin-provisioner-8cc8d794f-sb6wp   7/7     Running   0             6m59s
csi-rbdplugin-provisioner-8cc8d794f-wfzdr   0/7     Pending   0             6m59s
csi-rbdplugin-xx7zq                         3/3     Running   0             6m59s
pod-with-raw-block-volume                   1/1     Running   0             3m15s
rbd-provisioner-76f6bc6669-4kpgs            1/1     Running   3 (11h ago)   14h
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl -n ns-wordpress exec -ti pod-with-raw-block-volume -- /bin/sh
sh-4.4# df -hP
Filesystem      Size  Used Avail Use% Mounted on
overlay          10G  6.1G  4.0G  61% /
tmpfs            64M     0   64M   0% /dev
tmpfs           1.9G     0  1.9G   0% /sys/fs/cgroup
/dev/xvda1       10G  6.1G  4.0G  61% /etc/hosts
shm              64M     0   64M   0% /dev/shm
tmpfs           3.7G   12K  3.7G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs           1.9G     0  1.9G   0% /proc/acpi
tmpfs           1.9G     0  1.9G   0% /proc/scsi
tmpfs           1.9G     0  1.9G   0% /sys/firmware
sh-4.4#
  • En el siguiente POD, vamos a crear un FS /var/lib/www/html dentro de un contenedor de nginx apuntando al mismo pool de Ceph:
[[email protected] wpceph2]# cat pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: rbd-pvc
  namespace: ns-wordpress
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 1Gi
  storageClassName: csi-rbd-sc
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f pvc.yaml
persistentvolumeclaim/rbd-pvc created
[[email protected] wpceph2]#

[[email protected] wpceph2]# cat pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: csi-rbd-demo-pod
  namespace: ns-wordpress
spec:
  containers:
    - name: web-server
      image: nginx
      volumeMounts:
        - name: mypvc
          mountPath: /var/lib/www/html
  volumes:
    - name: mypvc
      persistentVolumeClaim:
        claimName: rbd-pvc
        readOnly: false
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl apply -f pod.yaml
pod/csi-rbd-demo-pod created
[[email protected] wpceph2]#

[[email protected] wpceph2]# kubectl get pod -n ns-wordpress
NAME                                        READY   STATUS    RESTARTS      AGE
csi-rbd-demo-pod                            1/1     Running   0             15s
csi-rbdplugin-458zw                         3/3     Running   0             13m
csi-rbdplugin-provisioner-8cc8d794f-cxbtm   7/7     Running   0             13m
csi-rbdplugin-provisioner-8cc8d794f-sb6wp   7/7     Running   0             13m
csi-rbdplugin-provisioner-8cc8d794f-wfzdr   0/7     Pending   0             13m
csi-rbdplugin-xx7zq                         3/3     Running   0             13m
pod-with-raw-block-volume                   1/1     Running   0             9m59s
rbd-provisioner-76f6bc6669-4kpgs            1/1     Running   3 (11h ago)   14h
[[email protected] wpceph2]# kubectl -n ns-wordpress exec -ti csi-rbd-demo-pod -- /bin/sh
# df -hP |grep html
/dev/rbd0       976M  2.6M  958M   1% /var/lib/www/html
#

Por último, mostrar la versión de Ceph y de Kubernetes que he utilizado para montar esta prueba:

[[email protected] ~]# ceph --version
ceph version 16.2.7 (dd0603118f56ab514f133c8d2e3adfc983942503) pacific (stable)
[[email protected] ~]#

[[email protected] ~]# kubectl get nodes -o wide
NAME         STATUS   ROLES                  AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION                CONTAINER-RUNTIME
kubemaster   Ready    control-plane,master   13d   v1.22.4   10.0.1.93     <none>        CentOS Linux 7 (Core)   3.10.0-1062.12.1.el7.x86_64   docker://20.10.11
kubwrk1      Ready    <none>                 13d   v1.22.4   10.0.1.83     <none>        CentOS Linux 7 (Core)   3.10.0-1062.12.1.el7.x86_64   docker://20.10.11
kubwrk2      Ready    <none>                 13d   v1.22.4   10.0.1.135    <none>        CentOS Linux 7 (Core)   3.10.0-1062.12.1.el7.x86_64   docker://20.10.11
[[email protected] ~]#
COMPÁRTEME

Deja un comentario