Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

Olá! Recentemente, muitas ferramentas interessantes de automação foram lançadas tanto para construção de imagens Docker quanto para implantação no Kubernetes. Nesse sentido, decidi brincar com o GitLab, estudar a fundo suas capacidades e, claro, configurar o pipeline.

Este trabalho foi inspirado no site kubernetes.io, que é gerado a partir códigos-fonte automaticamente, e para cada solicitação de pool enviada, o robô gera automaticamente uma versão prévia do site com suas alterações e fornece um link para visualização.

Tentei construir um processo semelhante do zero, mas inteiramente baseado no Gitlab CI e em ferramentas gratuitas que estou acostumado a usar para implantar aplicativos no Kubernetes. Hoje finalmente vou contar mais sobre eles.

O artigo discutirá ferramentas como:
Hugo, qbec, Kaniko, git-crypt и CI do GitLab com a criação de ambientes dinâmicos.

Conteúdo

  1. Conheça Hugo
  2. Preparando o Dockerfile
  3. Conhecendo Kaniko
  4. Conhecendo o qbec
  5. Experimentando o Gitlab-runner com o executor Kubernetes
  6. Implantando gráficos Helm com qbec
  7. Apresentando o git-crypt
  8. Criando uma imagem de caixa de ferramentas
  9. Nosso primeiro pipeline e montagem de imagens por tags
  10. Automação de implantação
  11. Artefatos e montagem ao empurrar para dominar
  12. Ambientes dinâmicos
  13. Revisar aplicativos

1. Conhecendo Hugo

Como exemplo do nosso projeto, tentaremos criar um site de publicação de documentação construído no Hugo. Hugo é um gerador de conteúdo estático.

Para quem não está familiarizado com geradores estáticos, contarei um pouco mais sobre eles. Ao contrário dos motores de sites convencionais com banco de dados e algum PHP, que, quando solicitados por um usuário, geram páginas dinamicamente, os geradores estáticos são projetados de forma um pouco diferente. Eles permitem que você pegue as fontes, geralmente um conjunto de arquivos na marcação Markdown e nos modelos de tema, e depois os compile em um site totalmente finalizado.

Ou seja, como resultado, você receberá uma estrutura de diretórios e um conjunto de arquivos HTML gerados, que você pode simplesmente enviar para qualquer hospedagem barata e obter um site funcional.

Você pode instalar o Hugo localmente e testá-lo:

Inicializando um novo site:

hugo new site docs.example.org

E ao mesmo tempo o repositório git:

cd docs.example.org
git init

Até agora, nosso site está impecável e para que algo apareça nele, primeiro precisamos conectar um tema; um tema é apenas um conjunto de modelos e regras específicas pelas quais nosso site é gerado.

Para o tema usaremos Aprenda, que, na minha opinião, é perfeitamente adequado para um site de documentação.

Gostaria de prestar atenção especial ao fato de que não precisamos salvar os arquivos do tema no repositório do nosso projeto; em vez disso, podemos simplesmente conectá-lo usando submódulo git:

git submodule add https://github.com/matcornic/hugo-theme-learn themes/learn

Assim, nosso repositório conterá apenas arquivos diretamente relacionados ao nosso projeto, e o tema conectado permanecerá como um link para um repositório específico e um commit nele, ou seja, sempre pode ser extraído da fonte original e não ter medo de mudanças incompatíveis.

Vamos corrigir a configuração configuração.toml:

baseURL = "http://docs.example.org/"
languageCode = "en-us"
title = "My Docs Site"
theme = "learn"

Já nesta fase você pode executar:

hugo server

E no endereço http://localhost:1313/ confira nosso site recém-criado, todas as alterações feitas no diretório atualizam automaticamente a página aberta no navegador, muito conveniente!

Vamos tentar criar uma página de rosto em conteúdo/_index.md:

# My docs site

## Welcome to the docs!

You will be very smart :-)

Captura de tela da página recém-criada

Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

Para gerar um site, basta executar:

hugo

Conteúdo do diretório public / e será o seu site.
Sim, a propósito, vamos adicioná-lo imediatamente .gitignore:

echo /public > .gitignore

Não se esqueça de submeter nossas alterações:

git add .
git commit -m "New site created"

2. Preparando o Dockerfile

É hora de definir a estrutura do nosso repositório. Eu costumo usar algo como:

.
├── deploy
│   ├── app1
│   └── app2
└── dockerfiles
    ├── image1
    └── image2

  • arquivos docker/ — contém diretórios com Dockerfiles e tudo o que é necessário para construir nossas imagens Docker.
  • implantar/ — contém diretórios para implantar nossos aplicativos no Kubernetes

Assim, criaremos nosso primeiro Dockerfile ao longo do caminho dockerfiles/website/Dockerfile

FROM alpine:3.11 as builder
ARG HUGO_VERSION=0.62.0
RUN wget -O- https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_linux-64bit.tar.gz | tar -xz -C /usr/local/bin
ADD . /src
RUN hugo -s /src

FROM alpine:3.11
RUN apk add --no-cache darkhttpd
COPY --from=builder /src/public /var/www
ENTRYPOINT [ "/usr/bin/darkhttpd" ]
CMD [ "/var/www" ]

Como você pode ver, o Dockerfile contém dois A PARTIR DE, esse recurso é chamado construção em vários estágios e permite excluir tudo o que for desnecessário da imagem final do docker.
Assim, a imagem final conterá apenas escurohttpd (servidor HTTP leve) e public / — o conteúdo do nosso site gerado estaticamente.

Não se esqueça de submeter nossas alterações:

git add dockerfiles/website
git commit -m "Add Dockerfile for website"

3. Conhecendo Kaniko

Como construtor de imagens docker, decidi usar Kaniko, uma vez que seu funcionamento não requer um daemon docker, e a construção em si pode ser realizada em qualquer máquina e o cache pode ser armazenado diretamente no registro, eliminando assim a necessidade de um armazenamento persistente completo.

Para construir a imagem, basta executar o contêiner com executor Kaniko e passe o contexto de construção atual; isso também pode ser feito localmente, via docker:

docker run -ti --rm 
  -v $PWD:/workspace 
  -v ~/.docker/config.json:/kaniko/.docker/config.json:ro 
  gcr.io/kaniko-project/executor:v0.15.0 
  --cache 
  --dockerfile=dockerfiles/website/Dockerfile 
  --destination=registry.gitlab.com/kvaps/docs.example.org/website:v0.0.1

Onde registro.gitlab.com/kvaps/docs.example.org/website — o nome da sua imagem docker; após a construção, ela será automaticamente lançada no registro do docker.

Parâmetro --cache permite armazenar camadas em cache no registro do docker; para o exemplo dado, elas serão salvas em registro.gitlab.com/kvaps/docs.example.org/website/cache, mas você pode especificar outro caminho usando o parâmetro --cache-repo.

Captura de tela do docker-registry

Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

4. Conhecendo o qbec

Qbec é uma ferramenta de implantação que permite descrever declarativamente os manifestos do seu aplicativo e implantá-los no Kubernetes. Usar Jsonnet como sintaxe principal permite simplificar bastante a descrição de diferenças em vários ambientes e também elimina quase completamente a repetição de código.

Isso pode ser especialmente verdadeiro nos casos em que você precisa implantar um aplicativo em vários clusters com parâmetros diferentes e deseja descrevê-los declarativamente no Git.

O Qbec também permite renderizar gráficos do Helm passando-lhes os parâmetros necessários e, em seguida, operá-los da mesma maneira que os manifestos regulares, inclusive você pode aplicar várias mutações a eles, e isso, por sua vez, permite que você se livre da necessidade de use o ChartMuseum. Ou seja, você pode armazenar e renderizar gráficos diretamente do git, onde eles pertencem.

Como eu disse anteriormente, armazenaremos todas as implantações em um diretório implantar/:

mkdir deploy
cd deploy

Vamos inicializar nossa primeira aplicação:

qbec init website
cd website

Agora a estrutura da nossa aplicação está assim:

.
├── components
├── environments
│   ├── base.libsonnet
│   └── default.libsonnet
├── params.libsonnet
└── qbec.yaml

vamos dar uma olhada no arquivo qbec.yaml:

apiVersion: qbec.io/v1alpha1
kind: App
metadata:
  name: website
spec:
  environments:
    default:
      defaultNamespace: docs
      server: https://kubernetes.example.org:8443
  vars: {}

Aqui estamos interessados ​​principalmente em especificações.ambientes, qbec já criou um ambiente padrão para nós e pegou o endereço do servidor, bem como o namespace de nosso kubeconfig atual.
Agora, ao implantar para omissão ambiente, o qbec sempre implantará apenas no cluster Kubernetes especificado e no namespace especificado, ou seja, você não precisa mais alternar entre contextos e namespaces para realizar uma implantação.
Se necessário, você sempre pode atualizar as configurações deste arquivo.

Todos os seus ambientes estão descritos em qbec.yaml, e no arquivo params.libsonnet, onde diz onde obter os parâmetros para eles.

A seguir vemos dois diretórios:

  • componentes / — todos os manifestos da nossa aplicação serão armazenados aqui; eles podem ser descritos tanto em arquivos jsonnet quanto em arquivos yaml regulares
  • ambientes / — aqui descreveremos todas as variáveis ​​(parâmetros) para nossos ambientes.

Por padrão temos dois arquivos:

  • ambientes/base.libsonnet - conterá parâmetros comuns para todos os ambientes
  • ambientes/default.libsonnet — contém parâmetros substituídos para o ambiente omissão

vamos abrir ambientes/base.libsonnet e adicione parâmetros para nosso primeiro componente:

{
  components: {
    website: {
      name: 'example-docs',
      image: 'registry.gitlab.com/kvaps/docs.example.org/website:v0.0.1',
      replicas: 1,
      containerPort: 80,
      servicePort: 80,
      nodeSelector: {},
      tolerations: [],
      ingressClass: 'nginx',
      domain: 'docs.example.org',
    },
  },
}

Vamos também criar nosso primeiro componente componentes/website.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.website;

[
  {
    apiVersion: 'apps/v1',
    kind: 'Deployment',
    metadata: {
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      replicas: params.replicas,
      selector: {
        matchLabels: {
          app: params.name,
        },
      },
      template: {
        metadata: {
          labels: { app: params.name },
        },
        spec: {
          containers: [
            {
              name: 'darkhttpd',
              image: params.image,
              ports: [
                {
                  containerPort: params.containerPort,
                },
              ],
            },
          ],
          nodeSelector: params.nodeSelector,
          tolerations: params.tolerations,
          imagePullSecrets: [{ name: 'regsecret' }],
        },
      },
    },
  },
  {
    apiVersion: 'v1',
    kind: 'Service',
    metadata: {
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      selector: {
        app: params.name,
      },
      ports: [
        {
          port: params.servicePort,
          targetPort: params.containerPort,
        },
      ],
    },
  },
  {
    apiVersion: 'extensions/v1beta1',
    kind: 'Ingress',
    metadata: {
      annotations: {
        'kubernetes.io/ingress.class': params.ingressClass,
      },
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      rules: [
        {
          host: params.domain,
          http: {
            paths: [
              {
                backend: {
                  serviceName: params.name,
                  servicePort: params.servicePort,
                },
              },
            ],
          },
        },
      ],
    },
  },
]

Neste arquivo descrevemos três entidades Kubernetes de uma vez, são elas: desenvolvimento, e eficaz и Ingresso. Se quiséssemos, poderíamos colocá-los em componentes diferentes, mas nesta fase um será suficiente para nós.

sintaxe jsonnet é muito semelhante ao json normal, em princípio, o json regular já é um jsonnet válido, então no início pode ser mais fácil para você usar serviços online como yaml2json para converter seu yaml normal em json ou, se seus componentes não contiverem nenhuma variável, eles poderão ser descritos na forma de yaml regular.

Ao trabalhar com jsonnet Eu recomendo fortemente instalar um plugin para o seu editor

Por exemplo, existe um plugin para vim vim-jsonnet, que ativa o realce de sintaxe e executa automaticamente jsonnet fmt toda vez que você salva (requer jsonnet instalado).

Está tudo pronto, agora podemos começar a implantar:

Para ver o que obtivemos, vamos executar:

qbec show default

Na saída, você verá manifestos yaml renderizados que serão aplicados ao cluster padrão.

Ótimo, agora aplique:

qbec apply default

Na saída você sempre verá o que será feito em seu cluster, o qbec pedirá que você concorde com as alterações digitando y você poderá confirmar suas intenções.

Nosso aplicativo está pronto e implantado!

Se você fizer alterações, você sempre poderá fazer:

qbec diff default

para ver como essas mudanças afetarão a implantação atual

Não se esqueça de submeter nossas alterações:

cd ../..
git add deploy/website
git commit -m "Add deploy for website"

5. Experimentando o Gitlab-runner com o executor Kubernetes

Até recentemente eu só usava regular gitlab-runner em uma máquina pré-preparada (contêiner LXC) com shell ou docker-executor. Inicialmente, tínhamos vários desses executores definidos globalmente em nosso gitlab. Eles coletaram imagens do Docker para todos os projetos.

Mas como a prática tem mostrado, esta opção não é a mais ideal, tanto em termos de praticidade como de segurança. É muito melhor e ideologicamente mais correto ter executores separados implantados para cada projeto, ou mesmo para cada ambiente.

Felizmente, isso não é um problema, pois agora iremos implantar gitlab-runner diretamente como parte do nosso projeto no Kubernetes.

O Gitlab fornece um gráfico de leme pronto para implantar o gitlab-runner no Kubernetes. Então tudo que você precisa fazer é descobrir token de registro para o nosso projeto em Configurações -> CI / CD -> Corredores e passe para o leme:

helm repo add gitlab https://charts.gitlab.io

helm install gitlab-runner 
  --set gitlabUrl=https://gitlab.com 
  --set runnerRegistrationToken=yga8y-jdCusVDn_t4Wxc 
  --set rbac.create=true 
  gitlab/gitlab-runner

Em que:

  • https://gitlab.com — o endereço do seu servidor Gitlab.
  • yga8y-jdCusVDn_t4Wxc — token de registro do seu projeto.
  • rbac.create=true — fornece ao executor a quantidade necessária de privilégios para poder criar pods para executar nossas tarefas usando o executor kubernetes.

Se tudo for feito corretamente, você deverá ver um corredor registrado na seção Runners, nas configurações do seu projeto.

Captura de tela do corredor adicionado

Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

É tão simples? - sim, é simples assim! Chega de complicações com o registro manual de runners, a partir de agora os runners serão criados e destruídos automaticamente.

6. Implante gráficos Helm com QBEC

Desde que decidimos considerar gitlab-runner parte do nosso projeto, é hora de descrevê-lo em nosso repositório Git.

Poderíamos descrevê-lo como um componente separado site do Network Development Group, mas no futuro planejamos implantar cópias diferentes site do Network Development Group muitas vezes, ao contrário gitlab-runner, que será implantado apenas uma vez por cluster Kubernetes. Então, vamos inicializar um aplicativo separado para isso:

cd deploy
qbec init gitlab-runner
cd gitlab-runner

Desta vez não descreveremos as entidades do Kubernetes manualmente, mas usaremos um gráfico Helm pronto. Uma das vantagens do qbec é a capacidade de renderizar gráficos Helm diretamente de um repositório Git.

Vamos conectá-lo usando o submódulo git:

git submodule add https://gitlab.com/gitlab-org/charts/gitlab-runner vendor/gitlab-runner

Agora o diretório fornecedor/gitlab-runner Temos um repositório com um gráfico para o gitlab-runner.

De forma semelhante, você pode conectar outros repositórios, por exemplo, todo o repositório com gráficos oficiais https://github.com/helm/charts

Vamos descrever o componente componentes/gitlab-runner.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.gitlabRunner;

std.native('expandHelmTemplate')(
  '../vendor/gitlab-runner',
  params.values,
  {
    nameTemplate: params.name,
    namespace: env.namespace,
    thisFile: std.thisFile,
    verbose: true,
  }
)

O primeiro argumento a expandirHelmTemplate passamos o caminho para o gráfico, então params.valores, que pegamos dos parâmetros do ambiente, então vem o objeto com

  • nomeModelo - título de lançamento
  • namespace — namespace transferido para o leme
  • este ficheiro — um parâmetro obrigatório que passa o caminho para o arquivo atual
  • detalhado - mostra o comando modelo de leme com todos os argumentos ao renderizar o gráfico

Agora vamos descrever os parâmetros do nosso componente em ambientes/base.libsonnet:

local secrets = import '../secrets/base.libsonnet';

{
  components: {
    gitlabRunner: {
      name: 'gitlab-runner',
      values: {
        gitlabUrl: 'https://gitlab.com/',
        rbac: {
          create: true,
        },
        runnerRegistrationToken: secrets.runnerRegistrationToken,
      },
    },
  },
}

Nota corredorRegistrationToken pegamos de um arquivo externo segredos/base.libsonnet, vamos criá-lo:

{
  runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc',
}

Vamos verificar se tudo funciona:

qbec show default

se tudo estiver em ordem, podemos excluir nossa versão implantada anteriormente via Helm:

helm uninstall gitlab-runner

e implante-o da mesma maneira, mas através do qbec:

qbec apply default

7. Introdução ao git-crypt

Git-crypt é uma ferramenta que permite configurar criptografia transparente para seu repositório.

No momento, nossa estrutura de diretórios para gitlab-runner é semelhante a esta:

.
├── components
│   ├── gitlab-runner.jsonnet
├── environments
│   ├── base.libsonnet
│   └── default.libsonnet
├── params.libsonnet
├── qbec.yaml
├── secrets
│   └── base.libsonnet
└── vendor
    └── gitlab-runner (submodule)

Mas armazenar segredos no Git não é seguro, não é? Portanto, precisamos criptografá-los corretamente.

Normalmente, por causa de uma variável, isso nem sempre faz sentido. Você pode transferir segredos para qbec e por meio das variáveis ​​de ambiente do seu sistema de CI.
Mas é importante notar que também existem projetos mais complexos que podem conter muito mais segredos; transferi-los todos através de variáveis ​​de ambiente será extremamente difícil.

Além disso, neste caso eu não seria capaz de falar sobre uma ferramenta tão maravilhosa como git-crypt.

git-crypt Também é conveniente porque permite salvar todo o histórico de segredos, bem como comparar, mesclar e resolver conflitos da mesma forma que estamos acostumados a fazer no caso do Git.

Primeira coisa após a instalação git-crypt precisamos gerar chaves para nosso repositório:

git crypt init

Se você tiver uma chave PGP, poderá adicionar-se imediatamente como colaborador deste projeto:

git-crypt add-gpg-user [email protected]

Desta forma, você sempre pode descriptografar este repositório usando sua chave privada.

Se você não possui uma chave PGP e não espera por ela, você pode seguir o outro caminho e exportar a chave do projeto:

git crypt export-key /path/to/keyfile

Assim, quem tiver um produto exportado arquivo-chave será capaz de descriptografar seu repositório.

É hora de configurar nosso primeiro segredo.
Deixe-me lembrá-lo de que ainda estamos no diretório implantar/gitlab-runner/, onde temos um diretório segredos/, vamos criptografar todos os arquivos nele contidos, para isso criaremos um arquivo segredos/.gitattributes com o seguinte conteúdo:

* filter=git-crypt diff=git-crypt
.gitattributes !filter !diff

Como pode ser visto no conteúdo, todos os arquivos estão mascarados * será conduzido através git-crypt, exceto a maioria .gitatributos

Podemos verificar isso executando:

git crypt status -e

A saída será uma lista de todos os arquivos no repositório para os quais a criptografia está habilitada

Isso é tudo, agora podemos submeter nossas alterações com segurança:

cd ../..
git add .
git commit -m "Add deploy for gitlab-runner"

Para bloquear um repositório, basta executar:

git crypt lock

e imediatamente todos os arquivos criptografados se transformarão em algo binário, será impossível lê-los.
Para descriptografar o repositório, execute:

git crypt unlock

8. Crie uma imagem de caixa de ferramentas

Uma imagem de caixa de ferramentas é uma imagem com todas as ferramentas que usaremos para implantar nosso projeto. Ele será usado pelo executor Gitlab para executar tarefas típicas de implantação.

Tudo é simples aqui, vamos criar um novo dockerfiles/caixa de ferramentas/Dockerfile com o seguinte conteúdo:

FROM alpine:3.11

RUN apk add --no-cache git git-crypt

RUN QBEC_VER=0.10.3 
 && wget -O- https://github.com/splunk/qbec/releases/download/v${QBEC_VER}/qbec-linux-amd64.tar.gz 
     | tar -C /tmp -xzf - 
 && mv /tmp/qbec /tmp/jsonnet-qbec /usr/local/bin/

RUN KUBECTL_VER=1.17.0 
 && wget -O /usr/local/bin/kubectl 
      https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/linux/amd64/kubectl 
 && chmod +x /usr/local/bin/kubectl

RUN HELM_VER=3.0.2 
 && wget -O- https://get.helm.sh/helm-v${HELM_VER}-linux-amd64.tar.gz 
     | tar -C /tmp -zxf - 
 && mv /tmp/linux-amd64/helm /usr/local/bin/helm

Como você pode ver, nesta imagem instalamos todos os utilitários que usamos para implantar nossa aplicação. Não precisamos disso aqui, a menos que kubectl, mas você pode querer brincar com ele durante a fase de configuração do pipeline.

Além disso, para podermos nos comunicar com o Kubernetes e implantar nele, precisamos configurar uma função para os pods gerados pelo gitlab-runner.

Para fazer isso, vamos ao diretório com gitlab-runner:

cd deploy/gitlab-runner

e adicione um novo componente componentes/rbac.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.rbac;

[
  {
    apiVersion: 'v1',
    kind: 'ServiceAccount',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
  },
  {
    apiVersion: 'rbac.authorization.k8s.io/v1',
    kind: 'Role',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
    rules: [
      {
        apiGroups: [
          '*',
        ],
        resources: [
          '*',
        ],
        verbs: [
          '*',
        ],
      },
    ],
  },
  {
    apiVersion: 'rbac.authorization.k8s.io/v1',
    kind: 'RoleBinding',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
    roleRef: {
      apiGroup: 'rbac.authorization.k8s.io',
      kind: 'Role',
      name: params.name,
    },
    subjects: [
      {
        kind: 'ServiceAccount',
        name: params.name,
        namespace: env.namespace,
      },
    ],
  },
]

Também descreveremos os novos parâmetros em ambientes/base.libsonnet, que agora se parece com isto:

local secrets = import '../secrets/base.libsonnet';

{
  components: {
    gitlabRunner: {
      name: 'gitlab-runner',
      values: {
        gitlabUrl: 'https://gitlab.com/',
        rbac: {
          create: true,
        },
        runnerRegistrationToken: secrets.runnerRegistrationToken,
        runners: {
          serviceAccountName: $.components.rbac.name,
          image: 'registry.gitlab.com/kvaps/docs.example.org/toolbox:v0.0.1',
        },
      },
    },
    rbac: {
      name: 'gitlab-runner-deploy',
    },
  },
}

Nota $.componentes.rbac.name refere-se a nome para componente rbac

Vamos verificar o que mudou:

qbec diff default

e aplique nossas alterações ao Kubernetes:

qbec apply default

Além disso, não se esqueça de submeter nossas alterações ao git:

cd ../..
git add dockerfiles/toolbox
git commit -m "Add Dockerfile for toolbox"
git add deploy/gitlab-runner
git commit -m "Configure gitlab-runner to use toolbox"

9. Nosso primeiro pipeline e montagem de imagens por tags

Na raiz do projeto criaremos .gitlab-ci.yml com o seguinte conteúdo:

.build_docker_image:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug-v0.15.0
    entrypoint: [""]
  before_script:
    - echo "{"auths":{"$CI_REGISTRY":{"username":"$CI_REGISTRY_USER","password":"$CI_REGISTRY_PASSWORD"}}}" > /kaniko/.docker/config.json

build_toolbox:
  extends: .build_docker_image
  script:
    - /kaniko/executor --cache --context $CI_PROJECT_DIR/dockerfiles/toolbox --dockerfile $CI_PROJECT_DIR/dockerfiles/toolbox/Dockerfile --destination $CI_REGISTRY_IMAGE/toolbox:$CI_COMMIT_TAG
  only:
    refs:
      - tags

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_TAG
  only:
    refs:
      - tags

Observe que usamos GIT_SUBMODULE_STRATEGY: normal para aqueles trabalhos em que você precisa inicializar explicitamente os submódulos antes da execução.

Não se esqueça de submeter nossas alterações:

git add .gitlab-ci.yml
git commit -m "Automate docker build"

Acho que podemos chamar isso com segurança de versão v0.0.1 e adicione a tag:

git tag v0.0.1

Adicionaremos tags sempre que precisarmos lançar uma nova versão. As tags nas imagens do Docker serão vinculadas às tags do Git. Cada push com uma nova tag inicializará a construção de imagens com esta tag.

Executar git push --tagse vejamos nosso primeiro pipeline:

Captura de tela do primeiro pipeline

Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

Vale a pena chamar sua atenção para o fato de que o assembly por tags é adequado para construir imagens docker, mas não é adequado para implantar uma aplicação no Kubernetes. Como novas tags podem ser atribuídas a commits antigos, neste caso, a inicialização do pipeline para eles levará à implantação da versão antiga.

Para resolver esse problema, geralmente a construção de imagens docker está vinculada a tags e a implantação do aplicativo em uma ramificação dominar, em que as versões das imagens coletadas são codificadas. É aqui que você pode inicializar o rollback com uma simples reversão dominar-galhos.

10. Automação de implantação

Para que o Gitlab-runner descriptografe nossos segredos, precisaremos exportar a chave do repositório e adicioná-la às nossas variáveis ​​de ambiente de CI:

git crypt export-key /tmp/docs-repo.key
base64 -w0 /tmp/docs-repo.key; echo

Salvaremos a linha resultante no Gitlab; para isso, vamos às configurações do nosso projeto:
Configurações -> CI / CD -> Variáveis

E vamos criar uma nova variável:

Formato
Chave
Valor
Protegido
Mascarado
Objetivo

File
GITCRYPT_KEY
<your string>
true (durante o treinamento você pode false)
true
All environments

Captura de tela da variável adicionada

Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

Agora vamos atualizar nosso .gitlab-ci.yml acrescentando a ele:

.deploy_qbec_app:
  stage: deploy
  only:
    refs:
      - master

deploy_gitlab_runner:
  extends: .deploy_qbec_app
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  before_script:
    - base64 -d "$GITCRYPT_KEY" | git-crypt unlock -
  script:
    - qbec apply default --root deploy/gitlab-runner --force:k8s-context __incluster__ --wait --yes

deploy_website:
  extends: .deploy_qbec_app
  script:
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes

Aqui habilitamos várias novas opções para qbec:

  • --root algum/aplicativo — permite determinar o diretório de um aplicativo específico
  • --force:k8s-context __incluster__ - esta é uma variável mágica que diz que a implantação ocorrerá no mesmo cluster em que o gtilab-runner está sendo executado. Isso é necessário porque, caso contrário, o qbec tentará encontrar um servidor Kubernetes adequado em seu kubeconfig
  • --espere — força o qbec a esperar até que os recursos que ele cria entrem no estado Pronto e só então saia com um código de saída bem-sucedido.
  • -sim - simplesmente desativa o shell interativo Você tem certeza? quando implantado.

Não se esqueça de submeter nossas alterações:

git add .gitlab-ci.yml
git commit -m "Automate deploy"

E depois git push veremos como nossos aplicativos foram implantados:

Captura de tela do segundo pipeline

Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

11. Artefatos e montagem ao empurrar para dominar

Normalmente, as etapas descritas acima são suficientes para construir e entregar quase todos os microsserviços, mas não queremos adicionar uma tag sempre que precisarmos atualizar o site. Portanto, seguiremos um caminho mais dinâmico e configuraremos uma implantação digest no branch master.

A ideia é simples: agora a imagem do nosso site do Network Development Group será reconstruído toda vez que você entrar dominare, em seguida, implantar automaticamente no Kubernetes.

Vamos atualizar esses dois jobs em nosso .gitlab-ci.yml:

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - mkdir -p $CI_PROJECT_DIR/artifacts
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_REF_NAME --digest-file $CI_PROJECT_DIR/artifacts/website.digest
  artifacts:
    paths:
      - artifacts/
  only:
    refs:
      - master
      - tags

deploy_website:
  extends: .deploy_qbec_app
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

Observe que adicionamos um tópico dominar к árbitros para empregos build_website e agora usamos $CI_COMMIT_REF_NAME ao invés de $CI_COMMIT_TAG, ou seja, estamos desvinculados das tags no Git e agora vamos enviar uma imagem com o nome do branch de commit que inicializou o pipeline. É importante notar que isso também funcionará com tags, o que nos permitirá salvar snapshots de um site com uma versão específica no docker-registry.

Quando o nome da docker tag para uma nova versão do site pode permanecer inalterado, ainda temos que descrever as alterações no Kubernetes, caso contrário ele simplesmente não reimplantará o aplicativo a partir da nova imagem, pois não notará nenhuma alteração no manifesto de implantação.

Opção —vm:ext-str digest=”$DIGEST” para qbec - permite passar uma variável externa para jsonnet. Queremos que ele seja reimplantado no cluster a cada versão do nosso aplicativo. Não podemos mais usar o nome da tag, que agora pode ser imutável, pois precisamos estar vinculados a uma versão específica da imagem e acionar a implantação quando ela mudar.

Aqui seremos ajudados pela capacidade de Kaniko de salvar uma imagem resumida em um arquivo (opção --arquivo de resumo)
Em seguida, transferiremos esse arquivo e o leremos no momento da implantação.

Vamos atualizar os parâmetros do nosso implantar/site/ambientes/base.libsonnet que agora ficará assim:

{
  components: {
    website: {
      name: 'example-docs',
      image: 'registry.gitlab.com/kvaps/docs.example.org/website@' + std.extVar('digest'),
      replicas: 1,
      containerPort: 80,
      servicePort: 80,
      nodeSelector: {},
      tolerations: [],
      ingressClass: 'nginx',
      domain: 'docs.example.org',
    },
  },
}

Pronto, agora qualquer commit em dominar inicializa a construção da imagem docker para site do Network Development Groupe, em seguida, implante-o no Kubernetes.

Não se esqueça de submeter nossas alterações:

git add .
git commit -m "Configure dynamic build"

Verificaremos mais tarde git push deveríamos ver algo assim:

Captura de tela do pipeline para mestre

Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

Em princípio, não precisamos reimplantar o gitlab-runner a cada push, a menos, é claro, que nada tenha mudado em sua configuração, vamos consertar isso em .gitlab-ci.yml:

deploy_gitlab_runner:
  extends: .deploy_qbec_app
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  before_script:
    - base64 -d "$GITCRYPT_KEY" | git-crypt unlock -
  script:
    - qbec apply default --root deploy/gitlab-runner --force:k8s-context __incluster__ --wait --yes
  only:
    changes:
      - deploy/gitlab-runner/**/*

alterar permitirá que você monitore as mudanças em implantar/gitlab-runner/ e acionará nosso trabalho somente se houver algum

Não se esqueça de submeter nossas alterações:

git add .gitlab-ci.yml
git commit -m "Reduce gitlab-runner deploy"

git push, isso é melhor:

Captura de tela do pipeline atualizado

Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

12. Ambientes dinâmicos

É hora de diversificar nosso pipeline com ambientes dinâmicos.

Primeiro, vamos atualizar o trabalho build_website no nosso .gitlab-ci.yml, removendo o bloco dele , o que forçará o Gitlab a acioná-lo em qualquer commit em qualquer branch:

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - mkdir -p $CI_PROJECT_DIR/artifacts
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_REF_NAME --digest-file $CI_PROJECT_DIR/artifacts/website.digest
  artifacts:
    paths:
      - artifacts/

Em seguida, atualize o trabalho implantar_website, adicione um bloco lá meio Ambiente:

deploy_website:
  extends: .deploy_qbec_app
  environment:
    name: prod
    url: https://docs.example.org
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

Isso permitirá que o Gitlab associe o trabalho com estímulo ambiente e exibir o link correto para ele.

Agora vamos adicionar mais dois trabalhos:

deploy_website:
  extends: .deploy_qbec_app
  environment:
    name: prod
    url: https://docs.example.org
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

deploy_review:
  extends: .deploy_qbec_app
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: http://$CI_ENVIRONMENT_SLUG.docs.example.org
    on_stop: stop_review
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply review --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG"
  only:
    refs:
    - branches
  except:
    refs:
      - master

stop_review:
  extends: .deploy_qbec_app
  environment:
    name: review/$CI_COMMIT_REF_NAME
    action: stop
  stage: deploy
  before_script:
    - git clone "$CI_REPOSITORY_URL" master
    - cd master
  script:
    - qbec delete review --root deploy/website --force:k8s-context __incluster__ --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG"
  variables:
    GIT_STRATEGY: none
  only:
    refs:
    - branches
  except:
    refs:
      - master
  when: manual

Eles serão lançados mediante envio para qualquer branch, exceto master, e implantarão a versão de visualização do site.

Vemos uma nova opção para qbec: --app-tag — permite marcar versões implantadas do aplicativo e trabalhar apenas dentro dessa tag; ao criar e destruir recursos no Kubernetes, o qbec operará apenas com eles.
Desta forma não podemos criar um ambiente separado para cada revisão, mas simplesmente reutilizar o mesmo.

Aqui também usamos qbec aplicar revisãoem vez de qbec aplicar padrão - este é exatamente o momento em que tentaremos descrever as diferenças para nossos ambientes (revisão e padrão):

Adicionar rever ambiente em implantar/site/qbec.yaml

spec:
  environments:
    review:
      defaultNamespace: docs
      server: https://kubernetes.example.org:8443

Então vamos declará-lo em implantar/website/params.libsonnet:

local env = std.extVar('qbec.io/env');
local paramsMap = {
  _: import './environments/base.libsonnet',
  default: import './environments/default.libsonnet',
  review: import './environments/review.libsonnet',
};

if std.objectHas(paramsMap, env) then paramsMap[env] else error 'environment ' + env + ' not defined in ' + std.thisFile

E anote os parâmetros personalizados para isso em implantar/site/ambientes/review.libsonnet:

// this file has the param overrides for the default environment
local base = import './base.libsonnet';
local slug = std.extVar('qbec.io/tag');
local subdomain = std.extVar('subdomain');

base {
  components+: {
    website+: {
      name: 'example-docs-' + slug,
      domain: subdomain + '.docs.example.org',
    },
  },
}

Vamos também dar uma olhada mais de perto em jobu stop_review, ele será acionado quando o branch for deletado e para que o gitlab não tente fazer o checkout ele é usado GIT_STRATEGY: nenhum, mais tarde clonamos dominar-branch e exclua a revisão através dele.
É um pouco confuso, mas ainda não encontrei uma maneira mais bonita.
Uma opção alternativa seria implantar cada avaliação em um namespace de hotel, que sempre pode ser totalmente demolido.

Não se esqueça de submeter nossas alterações:

git add .
git commit -m "Enable automatic review"

git push, git checkout -b teste, teste de origem git push, verificar:

Captura de tela dos ambientes criados no Gitlab

Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

Tudo está funcionando? - ótimo, exclua nosso branch de teste: mestre de checkout git, git push origem: teste, verificamos se os trabalhos de exclusão do ambiente funcionaram sem erros.

Aqui gostaria de esclarecer imediatamente que qualquer desenvolvedor de um projeto pode criar ramificações, ele também pode alterar .gitlab-ci.yml arquivo e acessar variáveis ​​secretas.
Portanto, é fortemente recomendado permitir a sua utilização apenas para ramos protegidos, por exemplo em dominarou crie um conjunto separado de variáveis ​​para cada ambiente.

13. Revise os aplicativos

Revisar aplicativos Este é um recurso do GitLab que permite adicionar um botão para cada arquivo no repositório para visualizá-lo rapidamente em um ambiente implantado.

Para que esses botões apareçam, você precisa criar um arquivo .gitlab/route-map.yml e descreva todas as transformações de caminho nele; no nosso caso será muito simples:

# Indices
- source: /content/(.+?)_index.(md|html)/ 
  public: '1'

# Pages
- source: /content/(.+?).(md|html)/ 
  public: '1/'

Não se esqueça de submeter nossas alterações:

git add .gitlab/
git commit -m "Enable review apps"

git pushe verifique:

Captura de tela do botão Avaliar aplicativo

Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

O trabalho está feito!

Fontes do projeto:

Obrigado pela atenção, espero que tenham gostado Experimentar novas ferramentas para criar e automatizar a implantação no Kubernetes

Fonte: habr.com

Adicionar um comentário