Versões de imagem automatizada com Jenkins, Packer e Kubernetes

Criar imagens personalizadas para inicializar instâncias do Compute Engine ou contêineres do Docker pode reduzir o tempo de inicialização e aumentar a confiabilidade. Com o software pré-instalado nessa imagem, você consegue reduzir a dependência de repositórios de terceiros sobre os quais não tem controle.

Você escolhe quanto do software e da configuração quer incluir nas imagens personalizadas. Por um lado, uma imagem de configuração mínima, chamada de imagem base neste documento, contém uma imagem base de SO, por exemplo, o Ubuntu 14.10, e também pode incluir software e configuração básicos. Por exemplo, é possível pré-instalar os tempos de execução de linguagens como Java ou Ruby, configurar a geração de registros remota ou aplicar os patches de segurança. A imagem base é estável e pode ser personalizada com mais recursos para ser usada com um aplicativo.

Por outro lado, uma imagem completamente configurada, chamada de imutável neste documento, contém não somente um SO ou imagem base, mas também tudo o que é necessário para executar um aplicativo. É possível incluir a configuração de tempo de execução na imagem, por exemplo, informações de conexão de banco de dados ou dados confidenciais. Ela também pode ser fornecida na inicialização por meio de variáveis de ambiente, metadados ou serviço de gerenciamento de chaves.

O processo de criação de imagens tem muito em comum com a criação de software: você tem o código (Chef, Puppet, bash etc.) e as pessoas que escrevem esse código. A criação ocorre quando você aplica o código a uma imagem base e, quando concluída, um artefato é gerado na saída. Em seguida, esse artefato é geralmente submetido a alguns testes. A maioria das práticas recomendáveis aplicadas na criação de software também são válidas para imagens: é possível controlar a versão para gerenciar os scripts de configuração, acionar criações quando esses scripts são alterados, executá-las automaticamente, definir a versão e testar os artefatos resultantes quando elas são concluídas.

O que você aprenderá

Nesta solução, você conhecerá duas abordagens gerais da criação de imagens personalizadas e aprenderá a usar várias ferramentas conhecidas de código aberto, incluindo Jenkins, Packer, Docker e Kubernetes, para criar um pipeline automatizado de criação contínua de imagens. Esse pipeline se integra ao Cloud Source Repositories no Google Cloud Platform (GCP) e resulta em imagens do Compute Engine e em imagens do Docker.

Você aprenderá como criar imagens básicas e imutáveis e conhecerá as práticas recomendadas para gerenciar o acesso a essas imagens em vários projetos no GCP. Por fim, um tutorial abrangente no final do documento permitirá implantar e usar uma implementação de referência de código aberto da solução.

Tipos de imagens

Na solução de aplicativos da Web escalonáveis e resilientes, um aplicativo da Web Ruby on Rails é usado como referência para a execução de aplicativos Web no GCP. O código-fonte para essa solução (em inglês) não usa imagens personalizadas. Quando uma instância do Compute Engine é inicializada, um script de inicialização instala o Chef Solo, que instala tudo o que é necessário para executar o aplicativo. Estão incluídos o nginx, Ruby 2, cURL e outras ferramentas de sistema, Unicorn, o aplicativo Rails e todas as gems, imagemagick e configurações dele.

No diagrama a seguir, veja a descrição do processo de inicialização.

Um diagrama do processo de inicialização sem uma imagem personalizada.

O processo não é rápido. Ele leva entre 10 e 15 minutos por instância para inicializar, dependendo da velocidade de download dos vários repositórios necessários para os pacotes. Além disso, todos esses repositórios precisam estar on-line e disponíveis. Nas seções a seguir, considere como o desempenho e a confiabilidade do processo de inicialização podem ser aprimorados com uma imagem base e uma imagem imutável.

Imagens base

Ao criar uma imagem base, escolha qual software e pacotes quer incluir nela. A seguir, alguns itens que precisam ser considerados nessa decisão:

  • Velocidade de instalação. A lentidão do download de pacotes grandes, a demora para criar o software a partir do código-fonte e pacotes com muitas dependências compõem o problema. Inclua esses tipos de software e pacotes na imagem base.
  • Confiabilidade do repositório remoto. Você confiará na disponibilidade do repositório remoto se o software não for incluído na imagem da fundação e, em vez disso, for transferido por download no momento da inicialização? Se ele não estiver disponível durante a inicialização, isso impedirá o funcionamento do aplicativo? Para reduzir a dependência de repositórios que não estão sob seu controle, inclua as dependências críticas na imagem base.
  • Taxa de mudança. O software ou pacote muda com muita frequência? Em caso afirmativo, considere deixar de fora uma imagem base e armazená-la em um local confiável e acessível, como um intervalo do Cloud Storage.
  • Obrigatoriedade ou necessidade de segurança. Se determinados pacotes (como registro, OSSEC etc.) tiverem autorização para ser executados (com uma configuração específica) em todas as instâncias da organização, esses pacotes deverão ser instalados em uma imagem de base estendida por todas as outras imagens. Uma equipe de segurança pode usar uma ferramenta mais avançada como o Chef ou Puppet para criar uma imagem de base Docker, enquanto os desenvolvedores de downstream usam um Dockerfile para estender a base com facilidade.

De acordo com esses critérios, uma sugestão de imagem base para o aplicativo Ruby on Rails da solução Aplicativos da Web escaláveis e resilientes incluiria Chef Solo, nginx, Ruby, cURL e outras ferramentas de sistema, além do Unicorn. As outras dependências seriam instaladas durante a inicialização.

No diagrama a seguir, veja a descrição do processo de inicialização com uma imagem base:

Um diagrama do processo de inicialização com uma imagem base.

Na instância funcional desse exemplo, a configuração (string de conexão de banco de dados, chaves de API etc.) é recuperada a partir do serviço de metadados do Compute Engine. É possível usar um serviço diferente, como o etcd (em inglês), ou um intervalo simples do Cloud Storage para gerenciar a configuração.

Nas seções a seguir, as ferramentas usadas para automatizar a criação dessa imagem base Ruby serão o foco.

Imagens imutáveis

Ao contrário da imagem base, uma imagem imutável contém todo o software incluído nela. Quando uma instância ou contêiner é iniciado a partir dessa imagem, não é preciso fazer o download de pacotes nem instalar nenhum software. Uma imagem imutável do aplicativo Ruby on Rails da solução Aplicativos da Web escaláveis e resilientes inclui todo o software, e a instância fica pronta para veicular o tráfego quando é inicializada.

Um diagrama do processo de inicialização com uma imagem imutável.

Configuração e imagens imutáveis

No aplicativo, os dados de configuração necessários podem ser acessados a partir de um serviço de configuração ou eles podem ser incluídos na imagem imutável. Se você escolher a segunda abordagem, pense no que implica, em termos de segurança, incluir chaves secretas nas imagens. Quando você envia imagens imutáveis para repositórios públicos no Docker Hub, elas são acessíveis por todos e não podem conter informações confidenciais ou chaves secretas.

Imagens imutáveis como unidade de implantação

Com as imagens imutáveis como unidade de implantação, você elimina a possibilidade de desvios na configuração, em que uma ou mais instâncias ficam em um estado diferente do esperado. Por exemplo, isso pode acontecer quando um patch de segurança é aplicado em cem contêineres em execução e uma falha ocorre na atualização de alguns deles. A imagem se transforma no que você implanta, quando qualquer alteração é realizada. Se o SO requer um patch de software ou a geração de registros precisa ser atualizada, você cria uma nova imagem com essas alterações e, para implantá-la, inicia os novos contêineres ou instâncias e substitui os existentes. Quando você inclui a configuração do aplicativo em uma imagem imutável, até uma simples alteração como a atualização da string de conexão de banco de dados significa que uma nova imagem precisa ser criada e liberada.

Arquitetura e implementação de um pipeline automatizado de criação de imagens

Esta seção inclui detalhes de implementação de um pipeline de criação automática de imagens que usa o Jenkins, o Packer, o Docker e o Google Kubernetes Engine (GKE) para criar automaticamente imagens personalizadas. Cada seção inclui uma introdução, o diagrama da arquitetura e a análise detalhada dos componentes.

Software e serviços usados

Estes são o software e serviços usados no criador automatizado de imagens.

Software Uso
Jenkins Jenkins é um servidor de integração contínua (CI, na sigla em inglês) conhecido e de código aberto. Ele é usado para verificar as alterações nos repositórios Git de outros projetos que contenham scripts de configuração de imagem. Em seguida, as imagens são criadas com base nesses repositórios.
Packer Packer é uma ferramenta de criação de imagens de máquina idênticas para várias plataformas a partir de uma única configuração de origem. Ela é compatível com várias fontes de configuração diferentes, incluindo Shell, Chef, Puppet, Ansible e Salt, além de gerar imagens para o Google Compute Engine, Docker e outros. O Packer é usado pelos agentes do Jenkins para criar as imagens com base na configuração nos repositórios Git.
Docker Docker é uma ferramenta de código aberto para a criação de pacotes e implantação de aplicativos como contêineres. A implantação do Jenkins nesta arquitetura e tutorial, incluindo o node líder e os agentes de criação, é feita por meio de contêineres Docker. As imagens Docker também são geradas nos agentes de criação como uma das opções de arquitetura.
GKE O GKE, desenvolvido com a tecnologia de código aberto Kubernetes, permite a execução e o gerenciamento de contêineres Docker em máquinas virtuais do GCP.
Container Registry O Container Registry fornece armazenamento seguro e particular de imagens do Docker no GCP. Ele é executado no GCP e é acessado por meio de um endpoint HTTPS.
Compute Engine O GKE usa VMs do Compute Engine para executar o Kubernetes e hospedar o líder do Jenkins e os contêineres do agente de compilação. O processo de criação do Jenkins também gera as imagens de VM do Compute Engine, além das imagens Docker.
Cloud Storage O Cloud Storage será usado para armazenar backups da configuração do Jenkins.
Nginx Com o Nginx, você tem a funcionalidade de proxy reverso, que encaminha as solicitações de entrada para a interface da Web do líder do Jenkins. É possível configurá-lo para encerrar as conexões SSL e fornecer a autenticação básica.

Visão geral do criador de imagens

No diagrama a seguir, você vê a interação de vários componentes para estruturar um sistema onde as imagens da VM e Docker são criadas automaticamente.

Um diagrama dos vários componentes do projeto de criador de imagens.

Defina um job no líder do Jenkins para cada imagem que pretende criar. As alterações são verificadas em um repositório de código-fonte (o Git, nessa ilustração) que contenha os scripts de configuração e um modelo do Packer com a descrição de como criar uma imagem. Quando uma alteração é detectada, o job é atribuído a um agente de criação no líder do Jenkins O agente usa o Packer para executar a versão, que gera uma imagem do Docker para o Container Registry e uma imagem da VM para o Compute Engine.

Packer e scripts de configuração

A criação de uma imagem está definida no modelo do Packer e nos scripts de configuração associados. Eles são tratados como software e armazenados no próprio repositório Git. Cada imagem que você cria tem o próprio repositório com um modelo Packer e os scripts de configuração.

Nesta seção, você encontra uma visão geral de uma possível configuração do Packer que usa o Chef para personalizar o Ubuntu 14.04, adicionando o Ruby e o rbenv. Para uma descrição completa do Packer, consulte a excelente documentação dele em https://www.packer.io/docs.

Nomenclatura da imagem e variáveis do Packer

Com o criador de imagens, uma imagem é criada sempre que uma alteração é feita no repositório Git que contém o modelo do Packer e os scripts de configuração da imagem. É recomendável nomear ou marcar a imagem com o branch e o código do commit do Git no qual ela foi criada. Com os modelos do Packer, você define as variáveis e fornece os valores em tempo de execução:

{
...
  "variables": {
      "Git_commit": "",
      "Git_branch": "",
      "ruby_version_name": "212",
      "project_id": "null"
  }
...
}

Com o agente de criação do Jenkins, você encontra o branch e o código do commit do Git, além de fornecer essas informações como variáveis para a ferramenta de linha de comando do Packer. Essa ação será abordada depois, na seção do tutorial deste documento.

Configuração programática com provisionadores

Em um modelo Packer, um ou mais provisionadores são definidos para descrever o uso de ferramentas como Chef, Puppet ou scripts de shell na configuração de uma instância. O Packer é compatível com vários provisionadores. Consulte o índice na documentação do Packer (em inglês) para uma lista completa. Neste snippet, você define um provisionador chef-solo com os caminhos do livro de receitas e as receitas a serem executadas para configurar uma imagem:

{
  ...
  "provisioners": [
    {
      "type": "chef-solo",
      "install_command": "apt-get install -y curl && curl -L https://www.opscode.com/chef/install.sh | {{if .Sudo}}sudo{{end}} bash",
      "cookbook_paths": ["chef/site-cookbooks"],
      "run_list": [{{
        "recipe[ruby]",
        "recipe[ruby::user]",
        "recipe[ruby::ruby212]"
      ]
    }
  ],
  ...
}

O livro de receitas do chef e as receitas ficam armazenados no mesmo repositório Git que o modelo do Packer.

Definir imagens de saída com criadores

Na seção builders do modelo, está definido o local onde os provisionadores são executados para criar novas imagens. Para criar uma imagem do Compute Engine e do Docker, defina dois criadores:

{
  "variables": {...},
  "provisioners": [...],
  "builders": [
    {
      "type": "googlecompute",
      "project_id": "{{user `project_id`}}",
      "source_image": "ubuntu-1410-utopic-v20150202",
      "zone": "us-central1-a",
      "image_name": "{{user `ruby_version_name`}}-{{user `Git_branch`}}-{{user `Git_commit`}}"
    },
    {
      "type": "docker",
      "image": "ubuntu:14.10",
      "commit": "true"
    }
  ],
 ...
}

O criador googlecompute inclui um atributo project_id, que indica o local em que a imagem resultante é armazenada. No atributo image_name, em que um nome é atribuído à imagem, as variáveis são concatenadas para a criação de um nome com informações sobre ela: a versão do Ruby, o branch e o código de commit do Git usados para criá-la. Um URI de amostra de uma imagem feita pelo criador googlecompute terá uma aparência semelhante a esta:

https://www.googleapis.com/compute/v1/projects/image-builder-project-name/global/images/ruby212-master-9909043

No caso do criador docker, é preciso incluir um atributo post-processors para marcar a imagem com o registro do Docker e o repositório a que essa imagem será enviada:

{
  "variables": {...},
  "provisioners": [...],
  "builders": [...],
  "post-processors": [
    [
      {
        "type": "docker-tag",
        "repository": "gcr.io/{{user `project_id`}}/ruby212",
        "tag": "{{user `Git_branch`}}-{{user `Git_commit`}}",
        "only": ["docker"]
      }
    ]
  ]
}

Esse pós-processador marcará a imagem para armazenamento no Container Registry usando o project_id fornecido quando a versão for executada. Depois que essa imagem do Docker for pressionada, ela poderá ser recuperada:

docker pull gcr.io/image-builder-project-name/ruby212:master-9909043

Cada imagem criada tem um modelo do Packer e scripts de configuração no próprio repositório. O líder do Jenkins tem um job definido para cada uma, como mostrado no diagrama a seguir.

Um diagrama do projeto do criador de imagens com imagens personalizadas.

Uma vantagem de usar o Jenkins junto com o Packer é que o Jenkins detecta e responde a qualquer atualização feita nos modelos ou scripts de configuração do Packer. Por exemplo, quando você atualiza a versão do Ruby instalada na imagem base do Ruby, a resposta do líder do Jenkins é atribuir um agente para clonar o repositório, executar o Packer no modelo e criar as imagens.

No tutorial no final desta solução, você encontrará os detalhes do processo de configuração de um job do Jenkins para executar a criação do Packer.

Isolamento do projeto

O líder do Jenkins e os agentes de criação são executados no mesmo projeto do Cloud Platform, e as imagens criadas por eles são armazenadas nesse projeto. Desse modo, você isola os aplicativos por função. Não há cobranças pelo projeto, você só é cobrado pelos recursos usados. Nesta solução, a infraestrutura do Jenkins é executada no próprio projeto, separada dos repositórios de código-fonte que ela usa. Os backups do Jenkins, abordados em uma seção posterior, são armazenados em um intervalo do Google Cloud Storage dentro do projeto. Com isso, o Jenkins atua como um "hub de imagens", compartilhando-as com outros projetos. Por outro lado, os outros projetos mantêm repositórios próprios com controles de acesso separados.

Como criar e compartilhar imagens em uma organização

Para facilitar o compartilhamento das imagens, nesta solução, cada imagem de versão é armazenada no Git em um projeto de configuração separado. Com isso, os projetos de criador de imagens ficam separados das imagens. Nessa arquitetura hub e spoke, em que o projeto de criador de imagens é o hub e os projetos de imagem são os spokes, as equipes separadas conseguem se apropriar e gerenciar as configurações de cada imagem com mais facilidade.

A arquitetura hub e spoke está ilustrada no seguinte a seguir.

Um diagrama do projeto do criador de imagens como um sistema hub e spoke.

O controle, em que o acesso a cada projeto de imagem é concedido para o cluster do Jenkins e o acesso às imagens criadas pelo Jenkins é concedido para outros projetos, será discutido mais adiante.

Um projeto por imagem

Cada projeto criado tem um Cloud Repository dedicado, baseado no Git. Não há limite para o número de projetos criados, e você só paga pelos recursos, por exemplo, as instâncias do Compute Engine, que são usados no projeto. Se você tiver imagens PHP, Ruby e Wordpress, cada uma poderá ter o próprio projeto visível no Console do Google Cloud Platform, como mostrado no diagrama a seguir.

Um diagrama do projeto do criador de imagens com projetos separados para cada imagem personalizada.

O Cloud Repository de um projeto é acessível pelo item de menu Código-fonte. Para novos projetos, escolha como inicializar o repositório: é possível espelhar um repositório GitHub ou Bitbucket atual, enviar um repositório Git local atual ou criar um novo repositório Git local a partir do Cloud Source Repositories, conforme mostrado na imagem a seguir.

Uma imagem da tela que mostra como procurar o código-fonte no Console do GCP.

Na imagem a seguir, veja o projeto de imagem base do Ruby inicializado com um modelo do Packer e receitas do Chef definindo a criação.

A imagem base do Ruby com o modelo do Packer e as receitas do Chef.

Para visualizar a URL para o repositório, clique em Configurações. Este URL será necessário ao criar um job de construção para o repositório no líder do Jenkins, conforme mostrado na imagem a seguir.

As configurações de repositório de código-fonte do líder do Jenkins.

Controle de acesso do Cloud Repository

O criador de imagens do Jenkins precisa das permissões Visualizar no Cloud Repository para cada projeto de configuração de imagem. No diagrama a seguir, você tem uma visão simplificada da arquitetura hub e spoke mostrada anteriormente.

Projeto de criador de imagens com as permissões necessárias.

Para cada projeto, o acesso precisa ser concedido ao projeto do criador de imagens do Jenkins usando o endereço de e-mail da conta de serviço de computação dele. O formato desse endereço é \{PROJECT_ID\}-compute@developer.gserviceaccount.com e ele está disponibilizado para cópia na seção "Permissões" do projeto no Console do GCP, como demonstrado na imagem abaixo.

Endereço a ser copiado da seção "Permissões" de um projeto.

Após conseguir o endereço de e-mail da conta de serviço de computação do projeto que executa o criador de imagens do Jenkins, acesse a seção Permissões de cada projeto com um Cloud Repository em que você quer criar imagens, selecione Adicionar membro e conceda a permissão Visualizar, como na imagem a seguir.

Configuração em um projeto da permissão "Pode visualizar".

Agora, o líder do Jenkins executado no projeto do criador de imagens consegue verificar e receber as alterações do Cloud Repository desses projetos, além de criar novas imagens quando o commit das alterações é feito.

Como compartilhar imagens do Compute Engine e do Docker

As imagens do Compute Engine e do Docker geradas no criador de imagens são armazenadas no mesmo projeto dele. Elas serão usadas por aplicativos de outros projetos para iniciar as instâncias do Compute Engine e os contêineres do Docker. Cada projeto de aplicativo precisa ter a permissão Pode visualizar para acessar o projeto do criador de imagens. Siga o processo definido na seção anterior mas, desta vez, localize a conta de serviço de computação de cada projeto de aplicativo e adicione-a como um membro com a permissão Pode visualizar no projeto do criador, como mostrado no diagrama a seguir.

Como adicionar outro projeto com permissão "Pode visualizar" no projeto de criador de imagens.

Backup e restauração do Jenkins

O líder do Jenkins inclui um job predefinido para o backup periódico da configuração e do histórico de jobs no Google Cloud Storage. Por padrão, ele é executado periodicamente, a cada duas horas, todos os dias da semana, como mostrado na imagem a seguir.

configurações de criação automatizada no líder do Jenkins.

Na etapa de criação do job, um script de shell é executado. Ele armazena chaves secretas, usuários, jobs e histórico em um tarball. Há duas cópias do arquivo criado: uma é nomeada com um carimbo de data e a outra recebe o nome de LATEST, o que torna simples e automática a restauração do backup mais recente. Nessa etapa, adicione ou remova itens do backup, como mostrado na imagem a seguir.

Como personalizar o script de criação.

Uma ação de pós-construção usa o plug-in do Cloud Storage e a credencial de metadados do Google que foi criada para interagir com as APIs do Google e enviar o arquivo de backup para o Cloud Storage. Ela faz o upload do arquivo com o carimbo de data e o LATEST. Na imagem a seguir, veja a definição da etapa.

Interface para definição das ações de pós-criação.

Na seguinte imagem, veja um intervalo com alguns backups acumulados:

Lista dos backups acumulados de um projeto.

Como restaurar um backup

Da mesma forma que as variáveis de ambiente são usadas para ativar o SSL ou autenticação básica no proxy reverso Nginx em uma seção anterior, é possível usar uma variável de ambiente para configurar o controlador de replicação Jenkins para que restaure um backup quando o serviço for iniciado. O seguinte código é um snippet dessa definição:

{
  "kind": "ReplicationController",
  ...
  "spec": {
    ...
    "template": {
      "spec": {
        "containers": [
            {
              "name": "jenkins",
              "env": [
                {
                  "name": "GCS_RESTORE_URL",
                  "value": "gs://your-backup-bucket/jenkins-backup/LATEST.tar.gz"
                }
              ],
             ...
           }
        ]
      }
    }
  }
}

Na imagem Docker do líder do Jenkins, a existência da variável de ambiente GCS_RESTORE_URL é verificada na inicialização. Se ela é encontrada, o valor é pressuposto como URL do backup, incluindo o esquema gs://, e a ferramenta de linha de comando gsutil instalada na imagem do líder é usada no script para fazer o download e restaurar o backup com segurança.

O processo de restauração só acontece quando um contêiner é iniciado. Para restaurar um backup depois de iniciar um líder do Jenkins, redimensione o controlador de replicação para 0, atualize a definição do controlador, de modo que ele aponte para o URL do backup, e defina o tamanho de volta para 1. Isso é descrito no tutorial.

Tutorial

O conteúdo completo do tutorial, incluindo as instruções e o código-fonte, está disponível no GitHub, em https://github.com/GoogleCloudPlatform/kube-jenkins-imager.

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…