¡Buenas fans de los contenedores! En este nuevo episodio vamos a ver cómo montar una de las piezas más olvidadas dentro de una infraestructura docker: el Registry. Cuando montamos una infraestructura de contenedores de cierto nivel las partes más sexys (self-healing, orquestación, ingress load balancing, self-healing… Ouh yeah, sigue hablándome sucio) se llevan toda la fama y las partes más básicas, pero «feas», las dejamos de lado. Sin embargo, sin un Registry propio no podríamos montar nuestro sexy sistema de clustering.
¿Que es un Registry?
Para los más despistados (como yo no hace tanto) vamos a empezar por lo más básico. Un Registry es un lugar donde almacenar imágenes de contenedores que luego utilizará(n) el/los engines para crear nuestros amados contenedores. Es lo que en el mundo pre-container hacía un repositorio de paquetes (rpm/apt/…) o para nuestros ene/amigos developers puede hacer npm o un repositorio de artefactos. Un Registry es una de las piezas clave a la hora de crear nuestros entornos Docker en cuanto empezamos a crear nuestras propias imágenes (minuto 1). Un Registry propio nos permitirá que los distintos motores docker que tengamos (¿he oído cluster docker?) puedan descargar las imágenes que desarrollemos y que no necesariamente queramos que sean públicas.
¿Y porqué montar el nuestro?
Desde Docker Inc. se ofrece un servicio de Registry que solemos usar a diario: Docker Hub. Este servicio se ofrece como SaaS y tiene una capa de uso gratuita. En este plan gratuito tenemos opción de un Registry privado y uno público. Si necesitamos más, podemos mirar su lista de precios que tampoco son muy elevados.
Entonces, ¿Por qué montar nuestro propio registro? Los motivos más evidentes son los siguientes:
- Tener el Registry en nuestra infraestructura nos ahorra ancho de banda y nos da mejor tiempo de acceso/descarga. Esto que puede parecer poco importante a día de hoy, en clusters donde lanzamos actualizaciones o entra en juego el self-healing tiene su importancia.
- Tener una copia «controlada» de imágenes públicas nos protege ante cambios inesperados, o desaparición, de las mismas (que os vamos a contar a los usuarios de npm).
- Tener tantos namespaces privados como queramos, con las ACL que queramos y la auth conectada contra nuestro sistema de auth preferido (ldap,…).
Y en el fondo porque a los BOFHers nos gusta controlar los servicios 😀
Para ilustrarlo vamos a ver un flujo de trabajo habitual:
- Desarrollamos un APP en el último lenguaje de moda, pongamos por ejemplo que es Trump Script 😀
- Hemos decidido que se va a desplegar sobre Docker. Más que nada porque como le pidamos a operaciones que nos instale el último commit de TS en sus sagrados servidores nos van a lanzar una mirada asesina de BOFH-er que nos va a dejar petrificados.
- Así que a la par que programamos, empezamos a crear nuestro Dockerfile. Empezamos tal que … FROM fulanodetal/lenguajemolon:latest …
- ¡WARNING! ¡Camino al abismo detectado! Tenemos un registry, así que vamos a intentar hacerlo bien. No tenemos más que hacer un pull de esa imagen, tagearla con nuestro Registry+namespace y hacer un push.
- Ahora sí, hacemos nuestro Dockerfile con un FROM registry.midominio.com/miproyecto/lenguajequemola:version … (recordad niños, SIEMPRE usamos un tag concreto, nunca latest)
- Hacemos el build y lo tageamos a nuestro registry ( docker build -t registry.midominio.com/miproyecto/miapp:VERSION ) y pusheamos
- Empezamos el ciclo normal de deploy en local/dev/stage/pre/prod
- La APP acaba en un Cluster Docker Rock solid.
- Todos somos felices, los cambios en la APP fluyen a PROD de manera estable y continua como el agua en un jardín japonés (clack, clack, clak …..).
¿Y qué es Como Dios Manda ™?
Ahora que estamos convencidos de montar nuestro Registry, ¿qué es lo que necesitamos para considerarlo «Como Dios Manda ™ » ? Vamos a tener que cumplir 3 mandamientos:
– SSL: A día de hoy no debería hacer falta argumentar esto. En este caso en concreto si no usamos SSL hay que tocar muchas cosas, tunear los engines, bla bla bla. SSL es obligatorio y punto.
– Auth/Authz: Queremos controlar quién (autenticación) puede (autorización) subir o bajar nuestras imágenes. Todo esto siempre ligado a espacios de nombres que nos den un control fino. Ya si lo conectamos con nuestro sistema previo… Miel sobre hojuelas.
– UI: Somos hackers, pero una UI que nos haga más amigable su gestión es algo que se agradece:D Además el cliente (si le damos acceso) siempre agradece estas cosas.
¿ Dónde lo ponemos ?
Entre los motivos que hemos mencionado para montar nuestro propio registry uno de ellos era el hecho de tenerlo «cerca» de nuestra infraestructura. El punto exacto ya dependerá de nuestras necesidades. Podemos tenerlo en nuestra cloud privada o en la cloud pública. Podemos tener uno para toda la organización o implementar uno por proyecto de suficiente entidad y que se ejecute dentro de la infraestructura del mismo. La idea es que sea tan sencillo de montar (o eso vamos a intentar) que la decisión no dependa de este factor. Eso sí, una cosa muy importante a tener en cuenta es la necesidad de almacenamiento: ¡Mucho!
¿ Cómo lo vamos a montar ?
Vamos a usar 3 piezas de software juntas que nos permitan alcanzar nuestro deseado registry como dios manda. Y todas ellas en forma de contenedor claro:
- Un Registry. Vamos a usar el propio registry de docker inc, en su versión 2. Lo podemos descargar desde docker hub: https://hub.docker.com/_/registry/.
- Un frontal nginx que se encarge de los certs y la gestión de los dominios.
- Una UI + auth/authz. Por suerte la gente de SUSE tiene liberado una buena pieza de software que hace todo esto: https://github.com/SUSE/Portus . Por desgracia la instalación está pensada para Suse, o a lo sumo instalación manual. Como nos encantan los contenedores vamos a instalarlo haciendo uso de Docker.
- Un backend de BBDD, MariadDB en este caso.
- Un agente que ejecuta tareas periódicas (cron).
Adicionalmente necesitamos un certificado SSL que nos sirva para dos dominios registry.midominio.com y portus.midominio.com. Para ello podemos usar un wildcard o usar certificados de let’s encrypt para pruebas. Pero esto último va más allá de este artículo, que si no no teminaremos nunca.
Vamos a ver un pequeño diagrama de lo que vamos a construir:
Para montarlo vamos a usar contenedores docker evidentemente. Vamos a usar docker-compose (por lo tanto necesitamos tenerlo instalado) para definir nuestro servicio. Sirva este post para ver un ejemplo de diseño de arquitectura software en un entorno de contenedores. En este caso vamos a usar la versión 2 de docker-compose file en lugar de la nueva (versión 3) que está pensada para entornos cluster. Nos parece más interesante no complicar en exceso el post y centrarnos en el Registry.
Nos tiramos al barro!
Vamos a empezar por montar un escenario que nos permita testarlo todo. Vamos a usar docker-machine y virtualbox para ello (dos dependencias solo para dev, en prod podemos montarlo como más tilín nos haga). Empezamos por crear un nuevo host docker:
docker-machine create -d virtualbox registry #esperamos a que termine de crear/arrancar eval $(docker-machine env registry)
A partir de ahora todos los comandos que lancemos en esa shell serán enviados al docker host. Hacemos un docker info para comprobar que todo está OK.
docker info Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 0 Server Version: 1.13.1 Storage Driver: aufs Root Dir: /mnt/sda1/var/lib/docker/aufs Backing Filesystem: extfs Dirs: 0 Dirperm1 Supported: true Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local Network: bridge host macvlan null overlay Swarm: inactive Runtimes: runc Default Runtime: runc Init Binary: docker-init containerd version: aa8187dbd3b7ad67d8e5e3a15115d3eef43a7ed1 runc version: 9df8b306d01f59d3a8029be411de015b7304dd8f init version: 949e6fa Security Options: seccomp Profile: default Kernel Version: 4.4.47-boot2docker Operating System: Boot2Docker 1.13.1 (TCL 7.2); HEAD : b7f6033 - Wed Feb 8 20:31:48 UTC 2017 OSType: linux Architecture: x86_64 CPUs: 1 Total Memory: 995.8 MiB Name: registry2 ID: U7CE:DTAH:NYMO:K5XL:YBWJ:YK7G:DX65:QAU4:NUOF:5GPM:M5C5:4JPV Docker Root Dir: /mnt/sda1/var/lib/docker Debug Mode (client): false Debug Mode (server): true File Descriptors: 14 Goroutines: 22 System Time: 2017-02-16T06:56:11.495893444Z EventsListeners: 0 Username: jlatorre Registry: https://index.docker.io/v1/ Labels: provider=virtualbox Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: false
Para que nuestra arquitectura funcione necesitamos los siguientes archivos de configuración:
- Configuración del Registry: /opt/registry/config.yml
- Cert para que el registry hable con Portus: /opt/registry/portus.crt
- Configuración de portus general: /opt/portus/config.yml
- Configuración de portus de BBDD: /opt/portus/database.yml
- Configuración de NGINX para el Registry: /opt/nginx/registry.irontec.com.conf
- Configuración de NGINX para el Portus: /opt/nginx/portus.irontec.com.conf
- Docker compose con la definición de la arquitectura: /opt/rcdm/docker-compose.yml
Vamos a empezar por preparar la estructura de directorios necesaria:
for DIR in certs mysql nginx portus rcdm registry registry_data do docker-machine ssh registry sudo mkdir -p /opt/$DIR docker-machine ssh registry sudo chgrp staff /opt/$DIR docker-machine ssh registry sudo chmod g+w /opt/$DIR done
Aparte de los dirs de los archivos de configuración hemos creado un directorio para los datos del Registry (/opt/registry_data) y otro para los de Mysql (/opt/mysql). En producción estos directorios (sobre el todo el registry_data) seguramente querramos que estén en una partición/disco dedicado.
Debemos copiar nuestro certificado válido para los dos subdominios que vamos a usar. En nuestro caso será wildcard, ya que disponemos de uno para nuestro dominio. Es importante que este certificado sea válido, ya que de lo contrario tendremos problemas a la hora de añadir el Registry al Portus.
openssl genrsa 2048 > wildcard.key Generating RSA private key, 2048 bit long modulus ............................................................................................+++ ..........................................................................+++ e is 65537 (0x10001) openssl req -new -x509 -nodes -sha1 -days 3650 -key wildcard.key > wildcard.crt You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:ES State or Province Name (full name) [Some-State]:Bizkaia Locality Name (eg, city) []:Bilbao Organization Name (eg, company) [Internet Widgits Pty Ltd]:Irontec: Internet y Sistemas sobre GNU / Linux Organizational Unit Name (eg, section) []:Sistemas Common Name (e.g. server FQDN or YOUR name) []:*.irontec.com Email Address []:[email protected]
Ahora ya podemos copiarlo:
docker-machine scp ./wildcard.crt registry:/opt/certs/ docker-machine scp ./wildcard.key registry:/opt/certs/
Con los siguientes comandos vamos a crear todos los archivos de configuración necesarios. Por comodidad vamos a lanzarlos desde la máquina virtual dónde está el engine Docker. Así que primero hacemos un ssh a la misma y luego un sudo. Acto seguido instalamos docker-compose que necesitaremos más adelante:
docker-machine ssh registry ## . ## ## ## == ## ## ## ## ## === /"""""""""""""""""\___/ === ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ / ===- ~~~ \______ o __/ \ \ __/ \____\_______/ _ _ ____ _ _ | |__ ___ ___ | |_|___ \ __| | ___ ___| | _____ _ __ | '_ \ / _ \ / _ \| __| __) / _` |/ _ \ / __| |/ / _ \ '__| | |_) | (_) | (_) | |_ / __/ (_| | (_) | (__| < __/ | |_.__/ \___/ \___/ \__|_____\__,_|\___/ \___|_|\_\___|_| Boot2Docker version 1.13.1, build HEAD : b7f6033 - Wed Feb 8 20:31:48 UTC 2017 Docker version 1.13.1, build 092cba3 docker@registry:~$ sudo -s root@registry:/home/docker# curl -L "https://github.com/docker/compose/releases/download/1.10.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose
Vamos con los archivos de configuración. Lo primero es crear la configuración del registry, la parte importante es la de notifications, donde le indicamos que notifique a Portus los nuevos push:
cat << EOL > /opt/registry/config.yml version: 0.1 storage: filesystem: rootdirectory: /registry_data delete: enabled: true http: addr: 0.0.0.0:5000 debug: addr: 0.0.0.0:5001 auth: token: rootcertbundle: /etc/docker/registry/portus.crt notifications: endpoints: - name: portus url: http://web:3000/v2/webhooks/events timeout: 500ms threshold: 5 backoff: 1s EOL
Necesitamos crear el cert que va usar el registry para conectarse al portus. Vamos a copiar un certe autofirmado que se incluye en la distribución de Portus. Evidentemente esto habría que cambiarlo en producción:
cat << EOL > /opt/registry/portus.crt -----BEGIN CERTIFICATE----- MIIFejCCA2ICAQEwDQYJKoZIhvcNAQEFBQAwgYIxCzAJBgNVBAYTAkRFMRAwDgYD VQQIDAdCYXZhcmlhMRMwEQYDVQQHDApOdWVyZW1iZXJnMQ0wCwYDVQQKDARTVVNF MRgwFgYDVQQDDA9wb3J0dXMudGVzdC5sYW4xIzAhBgkqhkiG9w0BCQEWFHJvb3RA cG9ydHVzLnRlc3QubGFuMB4XDTE1MDQxMjE1NTUwMloXDTI1MDQwOTE1NTUwMlow gYIxCzAJBgNVBAYTAkRFMRAwDgYDVQQIDAdCYXZhcmlhMRMwEQYDVQQHDApOdWVy ZW1iZXJnMQ0wCwYDVQQKDARTVVNFMRgwFgYDVQQDDA9wb3J0dXMudGVzdC5sYW4x IzAhBgkqhkiG9w0BCQEWFHJvb3RAcG9ydHVzLnRlc3QubGFuMIICIjANBgkqhkiG 9w0BAQEFAAOCAg8AMIICCgKCAgEAyWA25/CoT+VsvCbwwx71KQ9YRy5gzadOufi3 2t4NpP8O27tbemc4coIsEDLRBJSXxhBv97mTvfjAU4/nO0tJDgEHlrpl+p5IA6Up 3aYY2YqqY3riv+YI+e+RDcTau9Zd/ZxuB5OjpQocY16PGTP9dcUmn49oZ7xb3NUi eoDHp2cS9UaTUzjNrxR+z6GrhjkLE9k5j1hi48v75/Ee/jL6W7rEiajJbuQDBkxc mDmflalrrUAJnmCe1RpYRgbKEryBrFzUwBGsjqGwRnYwVNKc1CTnah986gj0Qx1O FiPexIQrumCKY9Z7FwBrTm+8Ip0zdwfRMz7qZ6zfJqjcj5/1lNpXC/mBJ5k2HLgj 6eGSuQTBLHJNMu5S0dtG1vGnhQF6RjM1f/K+vwOAinrUJx6bSV/guwBdo8zg6m/o krUvRAuP+l4ucyJP5T/JS53QXtJYSLNUdPVpec76EJOY1WrEBoyfdty2D3EHtnIF GpTesW0hD9Jz0ofLXBA3UCd+Gi/Wr2A0wzpn3VfONDqFa6xiljpT2YgBKpa1eucC +3JmVFRn6BY9jo76paC6Ygu/QzOfuF1nsv0aYdL9Lwdjf3HUBDFHUBJreJkh0QQ5 yZMXMhdFI4yEKvYJLAiA7tUwAQ6xvDegy+JOsRMsEvDRNNfueczEk345FlrqRXI4 KBPcdBsCAwEAATANBgkqhkiG9w0BAQUFAAOCAgEAGlCH3DFJJvOFrVO33Zp7lygq XMd/XDMOLG1gwJ1cVvZPVaNGKgcB/v1Rjhf9R39fxum5uvw005ZX+APj1rtOgkO/ fC9K0MA/kCIUjmU+NiH+UTDgcaChXXtVQ+PVAoWfKfEvwt6czcyQ4n+/hS0qJIjj vOuFpnI9VBOxgN85tnjBAZ/7PPxg8FoUss51wtRXmML45rCW77Q2NiGH717Mo110 xiue/+giTf7wP17Xl+Gvs4Fsm9rSDv0xhMYDjVbwU62ycQqXvDQVbbzkGjdNbKn2 Fzo/C8bCQOYuPzUo18b3PoplEkO/b780Lv7t7m9lTHAB4X81MO0yg8vNPrISK2Af VMJFDK4PsCdpGVFzY9Z+Jo5mGXV/n/nxRdaNujmANFeUl0Od1PuaDf+8w98GAuae mKTlyV6C5cPMVjwgDeGMdGj0yz7Ht/PXwy4KltHSzSrfUww9sr5F3Kcpekh2mcb2 NKXxXZ03b9AaWBPYEU2vD0N/MV7NwJqffW+/tLhMh/IVO991LTLFFKwZ31L+cHCj ozJubbxDwix8wjTYw+Vj6dJyZrqb3IfLDgl2+ReaF1i80CKm4e+iikK+dmC88Av8 FwdbTJL+QYEIxwHLz45cuHslqdD2josZYidrk1xBuQLMFN98jR+kwalmAT9dlSoA vUZzjl/Is5XRXOjaJNE= -----END CERTIFICATE----- EOL
Vamos ahora con la otra pata de nuestra arquitectura, Portus. Incluimos un archivo de configuración con muchas opciones deshabilitadas que en producción deberíamos ajustar (auth ldap, smtp, borrado de imagenes,…).
cat << EOL > /opt/portus/config.yml email: from: "[email protected]" name: "Portus" reply_to: "[email protected]" # If enabled, then SMTP will be used. Otherwise 'sendmail' will be used # (defaults to: /usr/sbin/sendmail -i -t). smtp: enabled: false address: "smtp.example.com" port: 587, user_name: "[email protected]" password: "password" domain: "example.com" gravatar: enabled: true delete: enabled: false ldap: enabled: false hostname: "ldap_hostname" port: 389 # Available options: "plain", "simple_tls" and "starttls". The default is # "plain", the recommended is "starttls". method: "plain" # The base where users are located (e.g. "ou=users,dc=example,dc=com"). base: "" # User filter (e.g. "mail=george*"). filter: "" # The LDAP attribute where to search for username. The default is 'uid'. uid: "uid" # LDAP credentials used to search for a user. authentication: enabled: false bind_dn: "" password: "" # Portus needs an email for each user, but there's no standard way to get # that from LDAP servers. You can tell Portus how to get the email from users # registered in the LDAP server with this configurable value. There are three # possibilities: # # - disabled: this is the default value. It means that Portus won't do a # thing when registering LDAP users (users will be redirected to their # profile page until they setup an email account). # - enabled where "attr" is empty: for this you need "ldap.base" to have # some value. In this case, the hostname will be guessed from the domain # component of the provided base string. For example, for the dn: # "ou=users,dc=example,dc=com", and a user name "user", the resulting # email is "[email protected]". # - enabled where "attr" is not empty: with this you specify the attribute # inside a LDIF record where the email is set. # # If something goes wrong when trying to guess the email, then it just falls # back to the default behavior (empty email). guess_email: enabled: false attr: "" first_user_admin: enabled: true signup: enabled: true check_ssl_usage: enabled: true registry: jwt_expiration_time: value: 5 catalog_page: value: 100 machine_fqdn: value: "<%= ENV['PORTUS_FQDN_VALUE'] %>" display_name: enabled: false user_permission: # Allow users to change the visibility or their personal namespace. If this is # disabled, only an admin will be able to change this. It defaults to true. change_visibility: enabled: true # Allow users to create/modify teams if they are an owner of it. If this is # disabled only an admin will be able to do this. This defaults to true. manage_team: enabled: true # Allow users to create/modify namespaces if they are an owner of it. If this # is disabled, only an admin will be able to do this. This defaults to true. manage_namespace: enabled: true EOL
Necesitamos también configurar la parte BBDD de Portus:
cat << EOL > /opt/portus/database.yml default: &default adapter: mysql2 encoding: utf8 <% if ENV['COMPOSE'] %> host: <%= ENV['PORTUS_DB_HOST'] %> username: root password: <%= ENV['PORTUS_DB_PASSWORD'] %> <% end %> development: <<: *default database: portus_development staging: <<: *default database: portus_staging # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default database: portus_test production: <<: *default <% if ENV["PORTUS_PRODUCTION_HOST"] %> host: <%= ENV["PORTUS_PRODUCTION_HOST"] %> <% end %> <% if ENV["PORTUS_PRODUCTION_USERNAME"] %> username: <%= ENV["PORTUS_PRODUCTION_USERNAME"] %> <% end %> <% if ENV["PORTUS_PRODUCTION_PASSWORD"] %> password: <%= ENV["PORTUS_PRODUCTION_PASSWORD"] %> <% end %> <% if ENV["PORTUS_PRODUCTION_DATABASE"] %> database: <%= ENV["PORTUS_PRODUCTION_DATABASE"] %> <% end %> EOL
Podemos ya definir el frontal Nginx. Creamos la configuración nginx para el vhost con Portus, que es bastante sencilla:
cat << EOL > /opt/nginx/portus.irontec.com.conf server { listen 443 ssl; server_name portus.irontec.com; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:20m; ssl_session_timeout 180m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; ssl_certificate /certs/wildcard.crt; ssl_certificate_key /certs/wildcard.key; location / { proxy_pass http://web:3000/; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } EOL
Creamos la conf nginx para el vhost con el Registry. Esta tiene algún ajuste más fino por la naturaleza de la comunicación que vamos a tener con el registry:
cat << EOL > /opt/nginx/registry.irontec.com.conf server { listen 443 ssl; server_name registry.irontec.com; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:20m; ssl_session_timeout 180m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; ssl_certificate /certs/wildcard.crt; ssl_certificate_key /certs/wildcard.key; ## # Docker-specific stuff. proxy_set_header Host \$http_host; # required for Docker client sake proxy_set_header X-Forwarded-Host \$http_host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Scheme \$scheme; # disable any limits to avoid HTTP 413 for large image uploads client_max_body_size 0; # required to avoid HTTP 411: see Issue #1486 # (https://github.com/docker/docker/issues/1486) chunked_transfer_encoding on; location / { proxy_pass http://registry:5000/; proxy_read_timeout 900; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; proxy_buffering on; auth_basic off; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } EOL
Ahora que tenemos la configuración individual de los servicios ya podemos crear un fichero compose donde definimos nuestra arquitectura:
cat << EOL > /opt/rcdm/docker-compose.yml version: '2' ## IMPORTANTE: antes de lanzar docker-compose hay que definir las siguientes variables: ## REGISTRY_FQDN ## PORTUS_FQDN ## DB_PASSWORD ## Por ejemplo: REGISTRY_FQDN="registry.midominio.com" PORTUS_FQDN="portus.midominio.com" DB_PASSWORD="misecreto" docker-compose up -d services: nginx: image: nginx:alpine restart: always ports: - 443:443 volumes: - /opt/nginx:/etc/nginx/conf.d:ro - /opt/certs:/certs:ro links: - registry - web web: image: irontec/portusweb:devel restart: always command: puma -b tcp://0.0.0.0:3000 -w 3 environment: - PORTUS_MACHINE_FQDN_VALUE=\$REGISTRY_FQDN - PORTUS_FQDN_VALUE=\$PORTUS_FQDN - PORTUS_DB_HOST=db - PORTUS_DB_PASSWORD=\$DB_PASSWORD volumes: - /opt/portus/database.yml:/portus/config/database.yml:ro - /opt/portus/config.yml:/portus/config/config.yml:ro ports: - 3000:3000 links: - db extra_hosts: - "registry.irontec.com:\$DOCKER_IP" crono: image: irontec/portusweb:devel restart: always entrypoint: bash ./bin/crono environment: - PORTUS_MACHINE_FQDN_VALUE=\$REGISTRY_FQDN - PORTUS_DB_HOST=db - PORTUS_DB_PASSWORD=\$DB_PASSWORD links: - db registry: image: library/registry:2.3.1 restart: always environment: - REGISTRY_AUTH_TOKEN_REALM=https://\$PORTUS_FQDN/v2/token - REGISTRY_AUTH_TOKEN_SERVICE=\$REGISTRY_FQDN - REGISTRY_AUTH_TOKEN_ISSUER=\$REGISTRY_FQDN volumes: - /opt/registry_data:/registry_data - /opt/registry:/etc/docker/registry:ro ports: - 5000:5000 - 5001:5001 # required to access debug service links: - web extra_hosts: - "portus.irontec.com:\$DOCKER_IP" db: image: library/mariadb:10.0.23 restart: always environment: MYSQL_ROOT_PASSWORD: \$DB_PASSWORD volumes: - /opt/mysql:/var/lib/mysql ports: - 3306:3306 EOL
Ya estamos listos para poder levantar nuestra arquitectura. ¡Vamos a ello! Como explica la cabecera del docker-compose.yml es necesario primero definir una serie de variables que haremos mediante un export:
cd /opt/rcdm #Definimos las variables necesarias: export REGISTRY_FQDN="registry.irontec.com" PORTUS_FQDN="portus.irontec.com" DB_PASSWORD="secreto" export DOCKER_IP="$(ip -one -4 a l dev eth1 | awk '{ print $4}' | awk -F '/' '{ print $1 }')" #Hacemos un pull primero para que se baje todas las imágenes, tardará un rato docker-compose pull #Ya podemos levantar docker-compose up -d
Vamos a comprobar si todo ha arrancado bien:
#Miramos si están UP, deberíamos ver 5 contenedores docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9d3f21f049ab nginx:alpine "nginx -g 'daemon ..." 11 seconds ago Up 10 seconds 80/tcp, 0.0.0.0:443->443/tcp rcdm_nginx_1 8687f1e215aa library/registry:2.3.1 "/bin/registry /et..." 11 seconds ago Up 10 seconds 0.0.0.0:5000-5001->5000-5001/tcp rcdm_registry_1 b2678f0c34a6 irontec/portusweb:devel "bash ./bin/crono" 12 seconds ago Up 11 seconds 3000/tcp rcdm_crono_1 98cf920b50c5 irontec/portusweb:devel "puma -b tcp://0.0..." 12 seconds ago Up 11 seconds 0.0.0.0:3000->3000/tcp rcdm_web_1 69aee69346ca library/mariadb:10.0.23 "/docker-entrypoin..." 13 seconds ago Up 12 seconds 0.0.0.0:3306->3306/tcp rcdm_db_1 #Miramos logs si fuese necesario docker-compose logs
Deberíamos tener 5 contenedores en status UP. Si vemos que alguno no arranca o se reinicia tendremos que hacer uso del comando docker logs rcdm_XXXX_1 para ver cuál es el motivo y solucionarlo.
Ya solo nos falta inicializar la base de datos. Para que Portus pueda empezar a funcionar es necesario crear la estructura de base de datos y alimentarla con unos pocos datos iniciales. Para esto, al ser una aplicación ruby, podemos lanzar un par de comandos rake que harán todo el trabajo. Para lanzarnos vamos a usar la definición de nuestro servicio portus (web), decirle a docker-compose que cree un docker (run) partiendo de esa configuración, ejecute el comando (rake ….) y al terminar elimine el contenedor creado (–rm).
#inicializamos la BBDD docker-compose run --rm web rake db:migrate:reset docker-compose run --rm web rake db:seed
Esta es una manera habitual de lanzar comandos o tareas en entornos de contenedores, donde fuera del contenedor no disponemos de las herramientas necesarias. Otro ejemplo suele ser el uso del comando cliente de mysql o mysqldump y situaciones similares.
Ahora sí deberíamos tener Portus UP & RUNNING. Nos salimos de la VM de docker engine (o usamos otra terminal) y añadimos a nuestro /etc/hosts la IP del docker host para que resuelva los 2 FQDNs y abrimos la web de portus.
echo "$(docker-machine ip registry) registry.irontec.com portus.irontec.com" | sudo tee -a /etc/hosts x-www-browser https://portus.irontec.com/
A jugar
Ya podemos empezar a jugar con nuestro registry/portus. Lo primero es entrar a la web de Portus crear un usuario (este primer usuario será admin, esto se ajusta en la conf de Portus creada hace unos momentos).
Una vez creado el usuario lo siguiente que nos pide Portus es que demos de alta el Registry. Importante: si hemos usado un cert autofirmado deberemos usar como hostname registry:5000 y deshabilitar el uso de SSL.
Y ahora ya podemos ver los namespace. Por defecto al crear un usuario se le asocia un namespace que vemos que está vacío.
Una vez tenemos Portus configurado podemos ahora conectar nuestro engine al registry…
docker login registry.irontec.com
… Y empezar por subir nuestra primera imagen:
docker pull alpine docker tag alpine registry.irontec.com/admin/alpine docker push registry.irontec.com/admin/alpine docker image rm registry.irontec.com/admin/alpine alpine docker run --rm -it registry.irontec.com/admin/alpine echo "Hola mundo"
Una vez pusheada podemos ir a Portus y ver de manera gráfica cómo se ha subido la imagen:
A partir de aquí podemos ir jugando. Probar los namespaces, crear más usuarios, crear grupos, asignar permisos en los namespaces a los grupos, tener namespaces públicos, webhooks, etc.
Disclaimer
Todo este post tiene solo un fin didáctico, por lo que la instalación que hemos hecho no está pensada para producción. Los que no hayáis hecho un CUT&PASTE furioso os habréis dado cuenta de que en el docker-compose se señala una imagen docker de propia cosecha irontec/portusweb:devel . Esta imagen, como claramente indica el tag, está pensada para desarrollo, generada directamente del git de portus y lanzada sin opciones de entorno de producción. También habréis notado que hay un certificado publicado directamente en el post y que es un cert autofirmado generado por los developers de Portus.
Otras opciones
Queda también como examen para el lector el explorar otras opciones de montar un «Registry Como Dios Manda ™». La más evidente para los usuarios de gitlab es hacer uso de su módulo registry. Para los usuarios de solución cloud cada proveedor ofrece un registry, que aunque no cumpla todas nuestras leyes para ser un «Registry Como Dios Manda ™», puede ser una buena solución. Amazon tiene el ECR y Google su Container Registry. También hay más UI-s para Registry, con diferentes grados de madurez. Finalmente Docker Inc, dentro de su solución «on premise» Docker Data Center, ofrece Docker Trusted Registry pero entraríamos ya en la necesidad de licenciamiento.
Fin
Y eso es todo por aquí amigos. Para ser mi primer post en el blog me ha salido un buen ladrillo sobre algo no muy sexy 🙂 Para próximas entradas espero poder traeros temas más interesante como clusterring, gestión de secretos y otras cosas bellas de Docker.
1 Comentario
¿Por qué no comentas tú también?
Me ha encantado el artículo!! Muy, muy sexy! 🙂
Jon Ander Hernández Hace 8 años
Como consigo más información? estoy por terminar mi carrera en computación en la http://www.ups.edu.ec/ y quiero hacer una especialización en el tema pero no sé en dónde ni que campos relacionados a mi malla me sirvan. Acá esta mi malla http://www.ups.edu.ec/computacion-cuenca si me ayudan. mil Gracias.
jame brow Hace 8 años
[…] el permiso de mi compañero Jon y su post montando un docker registry “Como Dios Manda ™” , ahora que ya hicimos una introducción, muy breve y desenfadada, a Docker y sabemos desarrollar […]
Creando un Docker Registry en nuestro GitLab - Blog Irontec Hace 5 años
Queremos tu opinión :)