Versões de imagens automatizadas com Jenkins, Packer e Kubernetes

Crie imagens personalizadas para inicializar as instâncias do Google Compute Engine ou os contêineres do Docker a fim de 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. Com esse pipeline integrado com o Git Cloud Repositories do Google Cloud Platform, você gera as imagens do Google Compute Engine e do Docker.

Você aprenderá a criar tanto imagens base como imutáveis e conhecerá as práticas recomendadas para gerenciar o acesso a elas por meio de vários projetos no Google Cloud Platform. Por fim, com um tutorial completo no final do documento, você implantará e usará uma implementação de referência da solução de código aberto.

Tipos de imagens

Na solução Aplicativos da Web escaláveis e resilientes, um aplicativo Ruby on Rails é usado como referência para executar os aplicativos da Web no Google Cloud Platform. No código-fonte dessa solução, as imagens personalizadas não são usadas. Quando uma instância do Google Compute Engine é inicializada, um script instala o Chef Solo que, em seguida, 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, pense em 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 da 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.
  • Estabilidade do repositório remoto: se você não inclui o software na imagem base e, em vez disso, faz o download durante a inicialização, você confia na disponibilidade do repositório remoto? 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 alterações: o software ou pacote é alterado 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.
  • Pacotes obrigatórios ou de segurança: quando a execução de determinados pacotes, por exemplo, geração de registros, OSSEC e outros, com uma configuração específica é obrigatória em todas as instâncias da sua organização, esses pacotes precisam ser instalados na imagem base que serve de referência para 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 que inclui, por exemplo, a string de conexão de banco de dados, chaves de API e outros, é recuperada a partir do serviço de metadados do Compute Engine. Se quiser, use outros serviços para gerenciar a configuração como o etcd ou um simples intervalo do Google Cloud Storage.

Na 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 canal automatizado de criação de imagens

Nesta seção, você encontra os detalhes da implementação de um canal automatizado de criação de imagens que usa Jenkins, Packer, Docker e Google Kubernetes Engine para criar imagens personalizadas automaticamente. 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 origens 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 a partir da 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 do Docker também são geradas nos agentes de criação como uma das opções de arquitetura.
Google Kubernetes Engine O Google Kubernetes Engine, com a tecnologia de código aberto do Kubernetes, permite executar e gerenciar contêineres do Docker nas máquinas virtuais do Google Cloud Platform.
Google Container Registry (gcr.io) Com o Google Container Registry, você tem armazenamento seguro e privado da imagem Docker no Google Cloud Platform. Ele é executado no Google Cloud Platform e acessado por meio de um ponto de extremidade HTTPS.
Google Compute Engine No Google Kubernetes Engine, as VMs do Compute Engine são usadas para executar o Kubernetes e hospedar o líder e os contêineres de agente de criação do Jenkins. O processo de criação do Jenkins também gera as imagens de VMs do Compute Engine, além das imagens do Docker.
Google Cloud Storage O Google Cloud Storage é usado para armazenar os 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 e executado com o Packer. Uma imagem Docker é gerada como saída para o Google Container Registry e uma de VM para o Google 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 nesta documentação para ver 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"]
      }
    ]
  ]
}

No pós-processador, a imagem é marcada para armazenamento no Google Container Registry com o project_id fornecido quando a criação é executada. Depois que a imagem Docker é enviada, você consegue recuperá-la com o seguinte comando:

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.

Para saber mais sobre como instalar e configurar o Jenkins no Kubernetes Engine, consulte Jenkins no Kubernetes Engine.

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ê tem imagens PHP, Ruby e Wordpress, cada uma pode 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: faça o espelhamento de um repositório do GitHub ou Bitbucket, envie um repositório local do Git ou crie um repositório local do Git a partir do Cloud Repository, como 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.

Clique no ícone de engrenagem de configuração para ver o URL do repositório. Isso será necessário para definir um job de criação para ele no líder do Jenkins, como 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 são usadas pelos 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. 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.

Na ação de pós-criação, o plug-in do Google Cloud Storage e a credencial de metadados do Google que você criou são usados para interagir com as APIs do Google e fazer o upload dos dois arquivos de backup para o Google Cloud Storage: o 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 maneira que você usa as variáveis de ambiente para ativar o SSL ou a autenticação básica no proxy reverso do Nginx, use uma variável para configurar a definição de controlador de replicação do líder do Jenkins. Assim, um backup é restaurado quando o serviço é 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…