Categorias
DevOps

Gitlab 12.1 e Dind 19.03: Resolvendo problema com conexão TLS entre CI e Docker Registry

Até a versão 12, o Gitlab CI conectava ao docker registry próprio usando conexão não segura (sem TLS) por padrão.

Com a versão 19.03 do Dind (Docker-in-Docker), a conexão com o repositório passou a utilizar TLS por padrão. Do nada seus jobs começarão a falhar com alguma das mensagens abaixo:

Error response from daemon: Client sent an HTTP request to an HTTPS server.
<code>time="2019-08-06T19:24:46Z" level=error msg="failed to dial gRPC: cannot connect to the Docker daemon. Is 'docker daemon' running on this host?: dial tcp <some address>:2375: connect: connection refused"</code>
Cannot connect to the Docker daemon at tcp://docker:2375/. Is the docker daemon running?

Para resolver isso, podemos desabilitar explicitamente o TLS (o que não é recomendado e não entrarei em detalhes) ou configurar o nosso executor (runner) para utilizar TLS e é isso que faremos.

[[runners]]
  name = "nome-do-seu-runner"
  url = "https://<seu dominio do gitlab>"
  token = "<SEU TOKEN>"
  executor = "docker"
  environment = ["DOCKER_HOST=tcp://docker:2376", "DOCKER_DRIVER=overlay2", "DOCKER_TLS_VERIFY=1", "DOCKER_CERT_PATH=/certs/${CI_JOB_ID}/client", "DOCKER_TLS_CERTDIR=/certs/${CI_JOB_ID}"]
  [runners.docker]
    pull_policy = "if-not-present"
    privileged = true
    disable_cache = false
    volumes = ["/tmp/gitlab/docker/certs:/certs", "/cache"]

Explicando:

  • DOCKER_HOST=tcp://docker:2376 : define o host interno do daemon e a porta no qual está conectado. A porta não-TLS (antiga padrão) é 2375. É ela que precisa ser alterada para 2376.
  • DOCKER_TLS_VERIFY=1 : define que o daemon deve verificar o certificado utilizado na comunicação.
  • DOCKER_TLS_CERTDIR=/certs/${CI_JOB_ID} : aqui definimos onde o serviço do docker irá gerar seus certificados. É um absoluto interno do container. Utilizamos a variável ${CI_JOB_ID} para que não haja conflito com execuções paralelas.
  • DOCKER_CERT_PATH=/certs/${CI_JOB_ID}/client : define o caminho onde o serviço deve procurar o certificado de cliente para conexão TLS. É um subdiretório gerado pela configuração anterior.
  • volumes = [“/tmp/gitlab/docker/certs:/certs”, “/cache”] : definimos um diretório no nosso host (máquina que possui o gitlab-runner instalado) que será compartilhado com os containers em execução para armazenar os certificados gerados.

Outra configuração importante que deve ser removida (e por isso não consta no meu exemplo) é a tls_verify = false .

E é isso, agora só reiniciar seu runner por garantia – embora ele já atualize as configurações automaticamente: sudo systemctl restart gitlab-runner

Referências:

Categorias
Containers

Usando valores de ARG e ENV na construção de imagens Docker

Esse é só um lembrete pro meu eu do futuro.

Para que uma variável definida no seu docker-compose.yml como args esteja disponível para seu Dockerfile e respectivos comandos (RUN/ENV), ele PRECISA ser enunciado no Dockerfile com o comando ARG.

No caso de enunciar antes da expressão FROM, você PRECISA repetir o enunciado após o mesmo – porque a imagem que você está estendendo pode ter removido/limpado as variáveis anteriores.
Segue um exemplo:

ARG PHP_VERSION
FROM phusion/baseimage:latest

ARG PHP_VERSION
RUN apt-get install php${PHP_VERSION}-cli
Categorias
Containers DevOps

Usando Docker para testar versões diferentes do MySQL

Recentemente assumi a tarefa de avaliar e otimizar algumas tabelas e queries que apresentavam perfomance muito diferente no PostgreSQL e no MySQL (versão bem antiga).

Minha primeira suspeita foi justamente o fato do MySQL estar muito desatualizado. Para descartar ou confirmar essa suspeita, eu precisava rodar no mesmo hardware versões diferentes do MySQL – e com containers, isso é fácil e indolor.

A minha instância padrão (versão antiga) compartilha com o host (minha máquina) a porta padrão do MySQL, então, do próprio macOS eu consigo conectar nela (depois de estar em execução) com o comando:

mysql -h 127.0.0.1 -P 3306 -u root -p

Agora, para executar uma versão específica, primeiro precisamos iniciar o container:

docker run --name mysql-atual -e MYSQL_ROOT_PASSWORD=senha -p 3307:3306 -d mysql:latest

Isso executará a versão atual (latest) da imagem mysql, definindo a senha senha para o usuário root, mapeando a porta 3306 do container com a 3307 do host (meu macOS) em modo daemon, com o nome mysql-atual.

Pronto, temos duas versões diferentes no mesmo hardware para fazer comparações. Podemos conectar nesse novo container da mesma forma que o anterior, alterando apenas a porta:

mysql -h 127.0.0.1 -P 3307 -u root -p
Categorias
DevOps

Qual o saldo de usar Docker no lugar do Vagrant para desenvolvimento?

Nos dois últimos posts falei um pouco do porquê e como fiz para gerar as imagens docker. Agora gostaria de compartilhar um pouco das vantagens que tenho visto nessa abordagem em relação ao Vagrant + VirtualBox.

Reprodutibilidade

Com o passar do tempo, a tendência é que as dependências de nossos projetos mudem, algumas coisas são adicionadas, umas removidas e outras atualizadas. Em um ambiente tradicional, acabamos por seguir o caminho mais rápido no dia a dia para adequar o ambiente, geralmente fazendo essa adaptação manualmente.

Dessa forma, se em algum momento você precisar recriar seu ambiente de desenvolvimento, ele provavelmente não funcionará como você esperava.

Como os containers Dockers são criados a partir da imagem a cada vez que executamos, qualquer alteração feita durante uma execução não é persistida – excluindo disso, obviamente, os volumes persistentes. Cada dia de trabalho, cada vez que você inicia seus containers é como o primeiro dia – quase poético.

As atualizações precisam necessariamente ser definidas no Dockerfile ou script correspondente para atualizar a imagem e tudo fica registrado.

Performance

Esse foi o ponto que me fez pensar em uma alternativa ao setup antigo – a escrita em disco era sofrível pelo VirtualBox. Além disso, como uso o mesmo ambiente para trabalhar em diversos projetos, há diversos serviços em execução ao mesmo tempo: MySQL Server, Postgresql Server, Elasticsearch, Redis Server, PHP-FPM (duas versões), Nginx, dentre outros.

Ficar alternando entre serviços ativos e inativos para cada projeto seria tedioso, e o preço era um consumo alto de memória.

Com o Docker e o docker-compose, tenho aliases no terminal para iniciar os serviços (leia-se containers) para cada projeto. Se mais de um projeto usa o mesmo serviço, ele é iniciado uma única vez e compartilhado. A escrita em disco com o recente Docker for Mac é muito mais que aceitável, especialmente quando comparada com o Vagrant.

Conclusão

De inicio eu nem enxerguei a vantagem inerente do uso de containers, mas é por si só um grande benefício dessa abordagem. A questão da performance é mais perceptível em relação a escrita de dados – banco de dados, processamento batch e etc. O consumo de memória vai depender muito da quantidade de serviços/containers rodando simultaneamente.

Categorias
DevOps

Como fiz o Gitlab gerar minhas imagens do Laradock

Já comentei diversas vezes que sou um usuário adito do Gitlab – como dizem por aí, desde quando tudo aquilo ali era mato. E um dos recursos que foram adicionados ao longo dos anos e eu mais utilizo é o de CI. Com ele consigo definir processos automáticos para testes, builds e deploy (esse, infelizmente, fora da minha realidade por enquanto) que são executados em containers ou máquinas especificamente designadas.

Se você olhar o repositório do Laradock, encontrará um arquivo com nome .gitlab-ci.yml. Esse arquivo é o responsável por dizer ao CI do Gitlab o que fazer quando uma alteração no repositório é enviada – ou quando o CI é disparado manualmente. Infelizmente ele não funcionará – a versão ali definida para o docker, dind e docker-compose estão defasadas, além disso, se você, como eu, utilizar o container registry do próprio Gitlab, essa informação deve ser adicionada ao arquivo.

O .gitlab-ci.yml atual do laradock começa desta forma:

# image: docker:latest
# services:
#   - docker:dind
image: jonaskello/docker-and-compose:1.12.1-1.8.0
services:
  - docker:1.12.1-dind

before_script:
  - docker info
  - docker-compose version
  - cp env-example .env
  - sed -i -- "s/=false/=true/g" .env
  - cat .env
  - env | sort

build:5.6:php-fpm:
  variables:
    PHP_VERSION: "5.6"
  script:
- docker-compose build php-fpm

Precisamos corrigir então:

  • Atualizar a imagem usada por: jonaskello/docker-and-compose:17.03.0-1.18.0
  • Atualizar a versão do service para: docker:18-dind
  • Incluir a variável DOCKER_HOST com valor tcp://docker:2375/
  • Incluir junto ao before_script a autenticação no seu registry para que seja possível fazer o push da imagem ao fim da geração. No meu caso, a linha fica: docker login -u gitlab-ci-token –password $CI_JOB_TOKEN registry.meudominio.com.br

Além disso, dentro do arquivo docker-compose.yml é preciso definir o caminho de cada imagem que você quer enviar ao container registry.

Por exemplo, no caso da imagem workspace as configurações devem ficar:

php-fpm:
      image: registry.meudominio.com.br/php-fpm:${PHP_VERSION}"
      build:
        context: ./php-fpm

Voltando ao .gitlab-ci.yml, em cada imagem que iremos construir devemos incluir o comando para fazer o push da imagem, todo o bloco de construção de uma imagem deve ficar parecido com o abaixo:

build:7.2:php-fpm:
  variables:
    PHP_VERSION: "7.2"
  script:
    - docker login -u gitlab-ci-token --password $CI_JOB_TOKEN registry.meudominio.com.br
    - docker pull "registry.meudominio.com.br/php-fpm:${PHP_VERSION}" || true
    - docker-compose build php-fpm
    - docker login -u gitlab-ci-token --password $CI_JOB_TOKEN registry.meudominio.com.br
    - docker-compose push php-fpm

Talvez você esteja perguntando por que tem duas chamadas ao docker login no processo de construção. A resposta é bem simples: garantir que não haja problema com sessão expirada. No meu setup, algumas imagens levam um bom tempo para serem construídas (culpa dos sistemas monolíticos) e no inicio eu tinha problema de ao tentar fazer o push da imagem, receber como resposta um Unauthorized.

Recapitulando, nosso .gitlab-ci.yml deve ficar parecido com:

image: jonaskello/docker-and-compose:17.03.0-1.18.0
services:
  - docker:18-dind

variables:
  DOCKER_HOST: tcp://docker:2375/
  DOCKER_DRIVER: overlay2

before_script:
  - docker login -u gitlab-ci-token --password $CI_JOB_TOKEN registry.meudominio.com.br
  - docker info
  - docker-compose version
  - cp env-example .env
  - cat .env
  - env | sort

build:7.2:php-fpm:
  variables:
    PHP_VERSION: "7.2"
  script:
    - docker login -u gitlab-ci-token --password $CI_JOB_TOKEN registry.meudominio.com.br
    - docker pull "registry.meudominio.com.br/php-fpm:${PHP_VERSION}" || true
    - docker-compose build php-fpm
    - docker login -u gitlab-ci-token --password $CI_JOB_TOKEN registry.meudominio.com.br
    - docker-compose push php-fpm

Último detalhe importante: o runner que vai executar esse build precisa ser do tipo Docker e deve ser executado em modo privilegiado (root), o que é não-recomendado em ambientes compartilhados – use uma máquina exclusivamente para CI, minimizando os riscos dessa configuração.
Agora você só precisa fazer o envio do seu código para instância do Gitlab que deseja usar e aguardar o build.

Se tiver alguma dica de como melhorar o setup, ou alguma dificuldade para fazer seu setup funcionar, deixe um comentário.