Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

Ola! Recentemente, lanzáronse moitas ferramentas de automatización interesantes tanto para crear imaxes de Docker como para a súa implantación en Kubernetes. Neste sentido, decidín xogar con GitLab, estudar a fondo as súas capacidades e, por suposto, configurar o pipeline.

Este traballo inspirouse na páxina web kubernetes.io, que se xera a partir de códigos fonte automaticamente, e para cada solicitude de grupo enviada, o robot xera automaticamente unha versión de vista previa do sitio cos teus cambios e proporciona unha ligazón para ver.

Tentei crear un proceso similar desde cero, pero construído enteiramente con Gitlab CI e ferramentas gratuítas que estou afeito usar para implementar aplicacións en Kubernetes. Hoxe por fin falareivos máis sobre eles.

O artigo discutirá ferramentas como:
Hugo, qbec, kaniko, git-crypt и GitLab CI coa creación de ambientes dinámicos.

Contido

  1. Coñece a Hugo
  2. Preparando o Dockerfile
  3. Coñecendo a kaniko
  4. Coñecendo qbec
  5. Probando Gitlab-runner con Kubernetes-executor
  6. Implementación de gráficos Helm con qbec
  7. Presentación de git-crypt
  8. Creación dunha imaxe de caixa de ferramentas
  9. O noso primeiro pipeline e montaxe de imaxes por etiquetas
  10. Automatización de implantación
  11. Artefactos e montaxe ao empurrar para dominar
  12. Ambientes dinámicos
  13. Revisar aplicacións

1. Coñecendo a Hugo

Como exemplo do noso proxecto, tentaremos crear un sitio de publicación de documentación construído sobre Hugo. Hugo é un xerador de contido estático.

Para aqueles que non estean familiarizados cos xeradores estáticos, vouvos contar un pouco máis sobre eles. A diferenza dos motores de sitios web convencionais con base de datos e algo de PHP, que, cando o solicita un usuario, xeran páxinas sobre a marcha, os xeradores estáticos están deseñados un pouco diferente. Permítenche coller fontes, normalmente un conxunto de ficheiros en modelos de marcas e temas de Markdown, e compilalos nun sitio web completamente rematado.

É dicir, como resultado, recibirá unha estrutura de directorios e un conxunto de ficheiros HTML xerados, que simplemente pode cargar a calquera hospedaxe barata e obter un sitio web que funcione.

Podes instalar Hugo localmente e probalo:

Iniciando un novo sitio:

hugo new site docs.example.org

E ao mesmo tempo o repositorio git:

cd docs.example.org
git init

Ata agora, o noso sitio é prístino e para que apareza algo nel, primeiro necesitamos conectar un tema; un tema é só un conxunto de modelos e regras especificadas polas que se xera o noso sitio.

Para o tema que utilizaremos Aprender, que, na miña opinión, é perfectamente axeitado para un sitio de documentación.

Gustaríame prestar especial atención ao feito de que non necesitamos gardar os ficheiros do tema no repositorio do noso proxecto; no seu lugar, simplemente podemos conectalo usando submódulo git:

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

Así, o noso repositorio conterá só ficheiros directamente relacionados co noso proxecto, e o tema conectado permanecerá como unha ligazón a un repositorio específico e un commit nel, é dicir, sempre se pode extraer da fonte orixinal e non ter medo de cambios incompatibles.

Imos corrixir a configuración config.toml:

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

Xa nesta fase podes executar:

hugo server

E no enderezo http://localhost:1313/ Consulte o noso sitio web recén creado, todos os cambios realizados no directorio actualizan automaticamente a páxina aberta no navegador, moi cómodo!

Tentemos crear unha portada content/_index.md:

# My docs site

## Welcome to the docs!

You will be very smart :-)

Captura de pantalla da páxina recén creada

Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

Para xerar un sitio, só tes que executar:

hugo

Contidos do directorio público/ e será o teu sitio web.
Si, por certo, engadímolo inmediatamente .gitignore:

echo /public > .gitignore

Non esquezas comprometer os nosos cambios:

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

2. Preparando o Dockerfile

É hora de definir a estrutura do noso repositorio. Normalmente uso algo como:

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

  • dockerfiles/ — conteñen directorios con Dockerfiles e todo o necesario para construír as nosas imaxes Docker.
  • despregar/ — contén directorios para implementar as nosas aplicacións en Kubernetes

Así, crearemos o noso primeiro Dockerfile ao longo do camiño 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 podes ver, o Dockerfile contén dous DE, esta oportunidade chámase construción en varias etapas e permítelle excluír todo o que non sexa necesario da imaxe docker final.
Así, a imaxe final só conterá escurohttpd (servidor HTTP lixeiro) e público/ — o contido do noso sitio web xerado de forma estática.

Non esquezas comprometer os nosos cambios:

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

3. Coñecendo kaniko

Como creador de imaxes docker, decidín usar kaniko, xa que o seu funcionamento non require un daemon docker, e a propia construción pódese realizar en calquera máquina e a caché pódese almacenar directamente no rexistro, eliminando así a necesidade de ter un almacenamento persistente completo.

Para crear a imaxe, só tes que executar o contedor con executor kaniko e pásalle o contexto de compilación actual; isto tamén se pode facer localmente, mediante 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 registry.gitlab.com/kvaps/docs.example.org/website — o nome da súa imaxe docker; despois da creación, lanzarase automaticamente no rexistro docker.

Parámetro --caché permítelle almacenar en caché capas no rexistro docker; para o exemplo dado, gardaranse registry.gitlab.com/kvaps/docs.example.org/website/cache, pero pode especificar outro camiño usando o parámetro --cache-repo.

Captura de pantalla de docker-registry

Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

4. Coñecendo qbec

Qbec é unha ferramenta de implementación que che permite describir de forma declarativa os manifestos da túa aplicación e implementalos en Kubernetes. Usar Jsonnet como sintaxe principal permíteche simplificar moito a descrición das diferenzas en varios ambientes e tamén elimina case por completo a repetición de código.

Isto pode ser especialmente certo nos casos nos que precisa implantar unha aplicación en varios clústeres con diferentes parámetros e quere describilos declarativamente en Git.

Qbec tamén permítelle renderizar gráficos Helm pasándolles os parámetros necesarios e despois operalos do mesmo xeito que os manifestos habituais, incluíndo que pode aplicarlle varias mutacións, e isto, á súa vez, permítelle desfacerse da necesidade de use ChartMuseum. É dicir, pode almacenar e renderizar gráficos directamente desde git, onde pertencen.

Como dixen anteriormente, almacenaremos todas as implementacións no directorio despregar/:

mkdir deploy
cd deploy

Imos inicializar a nosa primeira aplicación:

qbec init website
cd website

Agora a estrutura da nosa aplicación ten o seguinte aspecto:

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

vexamos o ficheiro qbec.yaml:

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

Aquí nos interesa principalmente espec.ambientes, qbec xa creou un ambiente predeterminado para nós e tomou o enderezo do servidor, así como o espazo de nomes do noso kubeconfig actual.
Agora ao implementar a defecto ambiente, qbec sempre se implementará só no clúster de Kubernetes especificado e no espazo de nomes especificado, é dicir, xa non terá que cambiar entre contextos e espazos de nomes para realizar unha implementación.
Se é necesario, sempre pode actualizar a configuración deste ficheiro.

Todos os teus ambientes descríbense en qbec.yaml, e no arquivo params.libsonnet, onde di onde obter os parámetros para eles.

A continuación vemos dous directorios:

  • compoñentes / — todos os manifestos da nosa aplicación almacenaranse aquí; pódense describir tanto en ficheiros jsonnet como en ficheiros yaml habituais
  • ambientes/ — aquí describiremos todas as variables (parámetros) dos nosos contornos.

Por defecto temos dous ficheiros:

  • ambientes/base.libsonnet - conterá parámetros comúns para todos os ambientes
  • ambientes/default.libsonnet — contén parámetros anulados para o ambiente defecto

imos abrir ambientes/base.libsonnet e engade alí parámetros para o noso primeiro compoñente:

{
  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',
    },
  },
}

Imos tamén crear o noso primeiro compoñente components/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 ficheiro describimos tres entidades de Kubernetes á vez, estas son: desenvolvemento, servizo и Ingreso. Se quixeramos, poderiamos poñelos en diferentes compoñentes, pero neste momento un será suficiente para nós.

sintaxe jsonnet é moi semellante ao json normal, en principio, o json normal xa é jsonnet válido, polo que ao principio pode ser máis doado usar servizos en liña como yaml2json para converter o teu yaml habitual en json ou, se os teus compoñentes non conteñen ningunha variable, pódense describir en forma de yaml normal.

Ao traballar con jsonnet Recomendo encarecidamente instalar un complemento para o teu editor

Por exemplo, hai un complemento para vim vim-jsonnet, que activa o resaltado de sintaxe e execútase automaticamente jsonnet fmt cada vez que garda (require jsonnet instalado).

Todo está listo, agora podemos comezar a implementar:

Para ver o que temos, imos correr:

qbec show default

Na saída, verá os manifestos yaml renderizados que se aplicarán ao clúster predeterminado.

Genial, agora aplica:

qbec apply default

Na saída sempre verá o que se fará no seu clúster, qbec pediralle que estea de acordo cos cambios escribindo y poderás confirmar as túas intencións.

A nosa aplicación está lista e implementada!

Se realizas cambios, sempre podes facer:

qbec diff default

para ver como afectarán estes cambios á implantación actual

Non esquezas comprometer os nosos cambios:

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

5. Probando Gitlab-runner con Kubernetes-executor

Ata hai pouco só usaba regular gitlab-runner nunha máquina preparada previamente (contedor LXC) con shell ou docker-executor. Inicialmente, tiñamos varios destes corredores definidos globalmente no noso gitlab. Recolleron imaxes docker para todos os proxectos.

Pero como demostrou a práctica, esta opción non é a máis idónea, tanto a nivel práctico como de seguridade. É moito mellor e ideoloxicamente máis correcto dispor de corredores separados para cada proxecto, ou mesmo para cada ambiente.

Afortunadamente, isto non é un problema en absoluto, xa que agora implementarémolo gitlab-runner directamente como parte do noso proxecto directamente en Kubernetes.

Gitlab ofrece un gráfico de timón preparado para implementar gitlab-runner en Kubernetes. Así que todo o que tes que facer é descubrir token de rexistro para o noso proxecto en Configuración -> CI / CD -> Corredores e pásao ao timón:

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

En que:

  • https://gitlab.com — o enderezo do seu servidor Gitlab.
  • yga8y-jdCusVDn_t4Wxc - token de rexistro para o teu proxecto.
  • rbac.create=true — proporciona ao corredor a cantidade necesaria de privilexios para poder crear pods para realizar as nosas tarefas usando kubernetes-executor.

Se todo está feito correctamente, deberías ver un corredor rexistrado na sección Runners, na configuración do teu proxecto.

Captura de pantalla do corredor engadido

Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

É tan sinxelo? -Si, é así de sinxelo! Non hai máis problemas co rexistro de corredores manualmente, a partir de agora os corredores crearanse e destruiranse automaticamente.

6. Implementa gráficos Helm con QBEC

Xa que decidimos considerar gitlab-runner parte do noso proxecto, é hora de describilo no noso repositorio Git.

Poderiamos describilo como un compoñente separado , pero no futuro temos previsto despregar diferentes copias moi a miúdo, a diferenza gitlab-runner, que só se implementará unha vez por clúster de Kubernetes. Entón, imos inicializar unha aplicación separada para iso:

cd deploy
qbec init gitlab-runner
cd gitlab-runner

Esta vez non imos describir as entidades de Kubernetes manualmente, pero levaremos un gráfico de Helm preparado. Unha das vantaxes de qbec é a capacidade de renderizar gráficos Helm directamente desde un repositorio de Git.

Conectámolo usando o submódulo git:

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

Agora o directorio vendedor/gitlab-runner Temos un repositorio cun gráfico para gitlab-runner.

Do mesmo xeito, pode conectar outros repositorios, por exemplo, todo o repositorio con gráficos oficiais https://github.com/helm/charts

Imos describir o compoñente components/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 para expandir HelmTemplate pasamos o camiño ao gráfico, entón parámetros.valores, que tomamos dos parámetros do entorno, despois vén o obxecto

  • Modelo de nome - título da publicación
  • espazo de nomes — espazo de nomes transferido a helm
  • este ficheiro — un parámetro obrigatorio que pasa a ruta ao ficheiro actual
  • detallada - mostra o comando modelo de timón con todos os argumentos ao renderizar o gráfico

Agora imos describir os parámetros do noso compoñente en 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,
      },
    },
  },
}

Preste atención RunnerRegistrationToken tomamos dun ficheiro externo secrets/base.libsonnet, imos crealo:

{
  runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc',
}

Comprobamos se todo funciona:

qbec show default

se todo está en orde, podemos eliminar a nosa versión previamente despregada a través de Helm:

helm uninstall gitlab-runner

e implementalo do mesmo xeito, pero a través de qbec:

qbec apply default

7. Introdución a git-crypt

Git-crypt é unha ferramenta que che permite configurar un cifrado transparente para o teu repositorio.

Neste momento, a nosa estrutura de directorios para gitlab-runner ten o seguinte aspecto:

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

Pero almacenar segredos en Git non é seguro, non é? Polo tanto, necesitamos cifralos correctamente.

Normalmente, por mor dunha variable, isto non sempre ten sentido. Podes transferir segredos a qbec e a través das variables de ambiente do seu sistema CI.
Pero vale a pena sinalar que tamén hai proxectos máis complexos que poden conter moitos máis segredos; transferilos todos a través de variables de ambiente será extremadamente difícil.

Ademais, neste caso non sería capaz de falarvos dunha ferramenta tan marabillosa como git-crypt.

git-crypt Tamén é conveniente porque permite gardar todo o historial de segredos, así como comparar, fusionar e resolver conflitos do mesmo xeito que estamos acostumados a facer no caso de Git.

Primeiro despois da instalación git-crypt necesitamos xerar claves para o noso repositorio:

git crypt init

Se tes unha clave PGP, podes engadirte inmediatamente como colaborador deste proxecto:

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

Deste xeito sempre podes descifrar este repositorio usando a túa clave privada.

Se non tes unha clave PGP e non a esperas, podes ir cara a outro lado e exportar a clave do proxecto:

git crypt export-key /path/to/keyfile

Así, calquera que teña un exportado ficheiro de chave poderá descifrar o seu repositorio.

É hora de establecer o noso primeiro segredo.
Permíteme lembrarche que aínda estamos no directorio deploy/gitlab-runner/, onde temos un directorio segredos/, imos cifrar todos os ficheiros nel, para iso crearemos un ficheiro segredos/.gitattributes co seguinte contido:

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

Como se pode ver no contido, todos os ficheiros están enmascarados * será conducido git-crypt, agás a maioría .gitattributes

Podemos comprobar isto executando:

git crypt status -e

A saída será unha lista de todos os ficheiros do repositorio para os que o cifrado está activado

Isto é todo, agora podemos confirmar os nosos cambios con seguridade:

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

Para bloquear un repositorio, só tes que executar:

git crypt lock

e inmediatamente todos os ficheiros cifrados converteranse en algo binario, será imposible lelos.
Para descifrar o repositorio, execute:

git crypt unlock

8. Crea unha imaxe de caixa de ferramentas

Unha imaxe da caixa de ferramentas é unha imaxe con todas as ferramentas que utilizaremos para implementar o noso proxecto. Usaráo o corredor de Gitlab para realizar tarefas típicas de implantación.

Todo é sinxelo aquí, imos crear un novo dockerfiles/toolbox/Dockerfile co seguinte contido:

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 podedes ver, nesta imaxe instalamos todas as utilidades que utilizamos para implantar a nosa aplicación. Non o necesitamos aquí a menos que kubectl, pero pode querer xogar con el durante a fase de configuración da canalización.

Ademais, para poder comunicarnos con Kubernetes e implementar nel, necesitamos configurar un rol para os pods xerados por gitlab-runner.

Para facelo, imos ao directorio con gitlab-runner:

cd deploy/gitlab-runner

e engade un novo compoñente components/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,
      },
    ],
  },
]

Tamén describiremos os novos parámetros en ambientes/base.libsonnet, que agora ten o seguinte aspecto:

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',
    },
  },
}

Preste atención $.components.rbac.name refírese a nome para compoñente rbac

Comprobamos o que cambiou:

qbec diff default

e aplique os nosos cambios a Kubernetes:

qbec apply default

Ademais, non esquezas confirmar os nosos cambios en 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. O noso primeiro pipeline e montaxe de imaxes por etiquetas

Na raíz do proxecto imos crear .gitlab-ci.yml co seguinte contido:

.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

Teña en conta que usamos GIT_SUBMODULE_STRATEGY: normal para aqueles traballos nos que precisa inicializar explícitamente os submódulos antes da execución.

Non esquezas comprometer os nosos cambios:

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

Creo que podemos chamar a isto unha versión con seguridade v0.0.1 e engade a etiqueta:

git tag v0.0.1

Engadiremos etiquetas sempre que necesitemos lanzar unha nova versión. As etiquetas das imaxes de Docker estarán ligadas ás etiquetas Git. Cada pulsación cunha nova etiqueta inicializará a creación de imaxes con esta etiqueta.

Fagámolo git push --tags, e vexamos o noso primeiro pipeline:

Captura de pantalla da primeira canalización

Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

Paga a pena chamar a súa atención sobre o feito de que a montaxe por etiquetas é adecuada para crear imaxes docker, pero non é adecuada para implementar unha aplicación en Kubernetes. Dado que se poden asignar novas etiquetas a commits antigas, neste caso, a inicialización da canalización para elas levará á implantación da versión antiga.

Para resolver este problema, normalmente a creación de imaxes docker está ligada a etiquetas e a implantación da aplicación nunha rama mestre, na que se codifican as versións das imaxes recollidas. Aquí é onde podes inicializar a restauración cun simple reverso mestre- ramas.

10. Automatización do despregamento

Para que Gitlab-runner descifra os nosos segredos, necesitaremos exportar a clave do repositorio e engadila ás nosas variables de ambiente CI:

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

Gardaremos a liña resultante en Gitlab; para facelo, imos á configuración do noso proxecto:
Configuración -> CI / CD -> Variables

E imos crear unha nova variable:

tipo
Clave
Valor
Protexido
enmascarado
Alcance

File
GITCRYPT_KEY
<your string>
true (durante o adestramento podes false)
true
All environments

Captura de pantalla da variable engadida

Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

Agora imos actualizar o noso .gitlab-ci.yml engadíndolle:

.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

Aquí activamos varias opcións novas para qbec:

  • --root some/app — permite determinar o directorio dunha aplicación específica
  • --force:k8s-context __incluster__ - esta é unha variable máxica que di que o despregamento terá lugar no mesmo clúster no que se está a executar gtilab-runner. Isto é necesario porque, en caso contrario, qbec tentará atopar un servidor Kubernetes axeitado no teu kubeconfig
  • --agarda — obriga a qbec a esperar ata que os recursos que crea pasen ao estado Listo e só despois saia cun código de saída exitoso.
  • —si - simplemente desactiva o shell interactivo Estás seguro? cando se despregue.

Non esquezas comprometer os nosos cambios:

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

E despois git push veremos como se implantaron as nosas aplicacións:

Captura de pantalla da segunda canalización

Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

11. Artefactos e montaxe ao empurrar para dominar

Normalmente, os pasos descritos anteriormente son suficientes para crear e ofrecer case calquera microservizo, pero non queremos engadir unha etiqueta cada vez que necesitemos actualizar o sitio. Polo tanto, tomaremos unha ruta máis dinámica e configuraremos un despregamento de resumo na rama mestra.

A idea é sinxela: agora a imaxe do noso será reconstruído cada vez que presione mestree, a continuación, implementarase automaticamente en Kubernetes.

Imos actualizar estes dous traballos no noso .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"

Teña en conta que engadimos un fío mestre к ref para postos de traballo build_website e agora usamos $CI_COMMIT_REF_NAME en vez de $CI_COMMIT_TAG, é dicir, estamos desvinculados das etiquetas en Git e agora empurraremos unha imaxe co nome da rama de commit que inicializou a canalización. Cómpre sinalar que isto tamén funcionará con etiquetas, o que nos permitirá gardar instantáneas dun sitio cunha versión específica no rexistro docker.

Cando o nome da etiqueta docker para unha nova versión do sitio non se pode modificar, aínda temos que describir os cambios en Kubernetes, se non, simplemente non volverá implementar a aplicación desde a nova imaxe, xa que non notará ningún cambio na imaxe. manifesto de implantación.

Opción —vm:ext-str digest="$DIGEST" para qbec: permítelle pasar unha variable externa a jsonnet. Queremos que se vuelva a implantar no clúster con cada versión da nosa aplicación. Xa non podemos usar o nome da etiqueta, que agora pode ser inmutable, xa que necesitamos estar ligados a unha versión específica da imaxe e activar a implantación cando esta cambie.

Aquí axudaranos a capacidade de Kaniko para gardar unha imaxe resumida nun ficheiro (opción --ficheiro-resumo)
Despois transferiremos este ficheiro e leremos no momento da implantación.

Actualicemos os parámetros do noso deploy/website/environments/base.libsonnet que agora quedará así:

{
  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',
    },
  },
}

Feito, agora calquera compromiso mestre inicializa a compilación da imaxe docker para , e despois implementalo en Kubernetes.

Non esquezas comprometer os nosos cambios:

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

Comprobaremos máis tarde git push deberíamos ver algo así:

Captura de pantalla da canalización para master

Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

En principio, non necesitamos volver a implantar gitlab-runner con cada empuxe, a menos que, por suposto, nada cambiou na súa configuración, imos corrixilo en .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/**/*

mudanzas permitirache supervisar os cambios en deploy/gitlab-runner/ e activará o noso traballo só se os hai

Non esquezas comprometer os nosos cambios:

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

git push, iso está mellor:

Captura de pantalla da canalización actualizada

Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

12. Contornas dinámicas

É hora de diversificar a nosa carteira con ambientes dinámicos.

Primeiro, imos actualizar o traballo build_website no noso .gitlab-ci.yml, quitándolle o bloque , o que obrigará a Gitlab a activalo en calquera commit en calquera rama:

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/

A continuación, actualiza o traballo sitio_web de implementación, engade un bloque alí 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"

Isto permitirá que Gitlab asocie o traballo produción ambiente e mostrar a ligazón correcta a el.

Agora imos engadir dous traballos máis:

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

Lanzaranse ao ser enviados a calquera rama excepto ao mestre e despregarán a versión de vista previa do sitio.

Vemos unha nova opción para qbec: --app-tag — permítelle etiquetar as versións implantadas da aplicación e traballar só con esta etiqueta; ao crear e destruír recursos en Kubernetes, qbec só funcionará con eles.
Deste xeito non podemos crear un ambiente separado para cada revisión, senón simplemente reutilizar o mesmo.

Aquí tamén usamos qbec aplicar revisión, en vez de qbec aplica por defecto - este é exactamente o momento no que tentaremos describir as diferenzas para os nosos ambientes (revisión e predeterminado):

Engadimos revisar ambiente en deploy/website/qbec.yaml

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

Despois declararémolo en deploy/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 iso deploy/website/environments/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',
    },
  },
}

Vexamos tamén máis de cerca jobu stop_review, activarase cando se elimine a rama e para que gitlab non intente facer a compra, úsase GIT_STRATEGY: ningunha, máis tarde clonamos mestre-ramificar e eliminar a revisión a través dela.
É un pouco confuso, pero aínda non atopei un xeito máis bonito.
Unha opción alternativa sería implementar cada revisión nun espazo de nomes de hotel, que sempre pode ser demolido por completo.

Non esquezas comprometer os nosos cambios:

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

git push, git checkout -b proba, proba de orixe git push, comproba:

Captura de pantalla dos contornos creados en Gitlab

Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

Todo funciona? - xenial, elimina a nosa rama de proba: git checkout master, git push origin :test, comprobamos que os traballos de eliminación do ambiente funcionaron sen erros.

Aquí gustaríame aclarar inmediatamente que calquera desenvolvedor nun proxecto pode crear ramas, tamén pode cambiar .gitlab-ci.yml arquivo e acceder a variables secretas.
Polo tanto, recoméndase encarecidamente permitir o seu uso só para ramas protexidas, por exemplo en mestreou cree un conxunto separado de variables para cada ambiente.

13. Revisar aplicacións

Revisar aplicacións Esta é unha función de GitLab que che permite engadir un botón para cada ficheiro do repositorio para visualizalo rapidamente nun ambiente despregado.

Para que aparezan estes botóns, cómpre crear un ficheiro .gitlab/route-map.yml e describe todas as transformacións de camiños nel; no noso caso será moi sinxelo:

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

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

Non esquezas comprometer os nosos cambios:

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

git push, e comproba:

Captura de pantalla do botón Revisar a aplicación

Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

O traballo está feito!

Fontes do proxecto:

Grazas pola vosa atención, espero que vos guste Probando novas ferramentas para crear e automatizar a implantación en Kubernetes

Fonte: www.habr.com

Engadir un comentario