¿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.

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

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

Y comprobamos que hemos descargado la imagen del contenedor correctamente con el comando 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] ~]#

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

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.

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

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.

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.
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 SÍ 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.

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.
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:

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:
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] ~]#

- 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] ~]#