Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes | | ДОСТУПНЫЙ ОТДЫХ
Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes
Интересное

Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

Привет! За последнее время вышло много классных инструментов автоматизации как для сборки Docker-образов так и для деплоя в Kubernetes. В связи с этим решил поиграться с гитлабом, как следует изучить его возможности и, конечно же, настроить пайплайн.

Вдохновлением для этой работы стал сайт kubernetes.io, который генерируется из исходных кодов автоматически, а на каждый присланный пул реквест робот автоматически генерирует preview-версию сайта с вашими изменениеми и предоставляет ссылку для просмотра.

Я постарался выстроить подобный процесс с нуля, но целиком построенный на Gitlab CI и свободных инструментах, которые я привык использовать для деплоя приложений в Kubernetes. Сегодня я, наконец, расскажу вам о них подробнее.

В статье будут рассмотрены такие инструменты как:
Hugo, QBEC, Kaniko, Git-crypt и GitLab CI с созданием динамических окружений.

Cодержание

  • Знакомство с Hugo
  • Подготовка Dockerfile
  • Знакомство с Kaniko
  • Знакомство с QBEC
  • Пробуем Gitlab-runner с Kubernetes-executor
  • Деплой Helm-чартов с QBEC
  • Знакомство с git-crypt
  • Создаём toolbox-образ
  • Наш первый пайплайн и сборка образов по тэгам
  • Автоматизация деплоя
  • Артефакты и сборка при push в master
  • Dynamic environments
  • Review Apps
  • 1. Знакомство с Hugo

    В качестве примера нашего проекта мы попробуем создать сайт для публикации документации, построенный на Hugo. Hugo — это статический генератор контента.

    Для тех, кто не знаком со статическими генераторами расскажу о них немного подробнее. В отличии от обычных движков сайтов с базой данных и каким-нибудь php, которые, при запросе пользователя, генерируют страницы на лету, статические генераторы устроенны немного иначе. Они позволяют взять исходники, как правило это набор файлов в Markdown-разметке и шаблоны тем, затем скомпилировать их в полностью готовый сайт.

    То есть на выходе вы получите структуру директорий и набор сгенерированных html-файлов, которые можно будет просто залить на любой дешёвый хостинг и получить рабочий сайт.

    Hugo можно установить локально и попробовать его в деле:

    Инициализируем новый сайт:

    hugo new site docs.example.org

    И заодно git-репозиторий:

    cd docs.example.org
    git init

    Пока что наш сайт девственно чист и чтобы на нём что-то появилось сначала нам нужно подключить тему, тема — всего лишь это набор темплейтов и заданных правил по которым генерируется наш сайт.

    В качестве темы мы будем использовать Learn, которая, на мой взгляд, как нельзя лучше подходит для сайта с документацией.

    Отдельное внимание хочется уделить тому, что нам не требуется сохранять файлы темы в репозитории нашего проекта, вместо этого мы можем просто подключить её используя git submodule:

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

    Таким образом в нашем репозитории будут находиться только файлы непосредственно относящиеся к нашему проекту, а подключенная тема останется в виде ссылки на конкретный репозиторий и коммит в нём, то есть её всегда можно будет вытянуть из оригинального источника и не бояться несовместимых изменений.

    Подправим конфиг config.toml:

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

    Уже на данном этапе можно запустить:

    hugo server

    И по адресу http://localhost:1313/ проверить наш только что созданный сайт, все изменения произведённые в директории автоматически обновляют и открытую страничку в браузере, очень удобно!

    Попробуем создать титульную страницу в content/_index.md:

    # My docs site

    ## Welcome to the docs!

    You will be very smart 🙂
    Скриншот только что созданной страницы

    Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

    Для генерации сайта достаточно запустить:

    hugo

    Содержимое директории public/ и будет являться вашим сайтом.
    Да, кстати, давайте сразу внесём её в .gitignore:

    echo /public > .gitignore

    Не забываем закоммитить наши изменения:

    git add .
    git commit -m «New site created»

    2. Подготовка Dockerfile

    Настало время определить структуру нашего репозитория. Обычно я использую что-то вроде:

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

    • dockerfiles/ — содержат директории с Dockerfiles и всем необходимым для сборки наших docker-образов.
    • deploy/ — содержит директории для деплоя наших приложений в Kubernetes

    Таким образом наш первый Dockerfile мы создадим по пути 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» ]

    Как вы можете заметить, Dockerfile содержит два FROM, эта возможность называется multi-stage build и позволяет исключить из финального docker-образа всё ненужное.
    Таким образом финальный образ у нас будет содержать только darkhttpd (легковесный HTTP-сервер) и public/ — контент нашего статически сгенирированного сайта.

    Не забываем закоммитить наши изменения:

    git add dockerfiles/website
    git commit -m «Add Dockerfile for website»

    3. Знакомство с Kaniko

    В качестве сборщика docker-образов я решил использовать Kaniko, так как для его работы не требуется наличие docker-демона, а саму сборку можно проводить на любой машине и хранить кэш прямо в registry, избавлясь, тем самым, от необходимости иметь полноценное persistent-хранилище.

    Для сборки образа достаточно запустить контейнер с kaniko executor и передать ему текущий контекст сборки, сделать это можно и локально, через 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

    Где registry.gitlab.com/kvaps/docs.example.org/website — имя вашего docker-образа, после сборки он будет автоматически запушен в docker-регистри.

    Параметр —cache позволяет кэшировать слои в docker registry, для приведённого примера они будут сохраняються в registry.gitlab.com/kvaps/docs.example.org/website/cache, но вы можете указать и другой путь с помощью параметра —cache-repo.

    Скриншот docker-registry

    Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

    4. Знакомство с QBEC

    Qbec — это инструмент деплоя, который позволяет декларативно описывать манифесты вашего приложения и деплоить их в Kubernetes. Использование Jsonnet в качестве основного синтаксиса позволяет очень сильно упростить описание различий для нескольких окружений, а также почти полностью избавляет от повторяемости кода.

    Это может быть особенно актуально в тех случаях, когда вам нужно задеплоить приложение в несколько кластеров с разными параметрами и вы хотите декларативно описать их в Git.

    Qbec также позволяет рендерить Helm-чарты передавая им необходимые параметры и в дальнейшем оперировать ими также как и обычными манифестами, в том числе можно накладывать на них различные мутации, а это, в свою очередь, позволяет избавиться от необходимости использовать ChartMuseum. То есть можно хранить и рендерить чарты прямо из git, где им и самое место.

    Как я говорил раньше, все деплойменты мы будем хранить в директории deploy/:

    mkdir deploy
    cd deploy

    Давайте инициализируем наше первое приложение:

    qbec init website
    cd website

    Сейчас структура нашего приложения выглядит так:

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

    посмотрим на файл qbec.yaml:

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

    Здесь нас интересует в первую очередь spec.environments, qbec уже создал за нас default окружение и взял адрес сервера, а также namespace из нашего текущего kubeconfig.
    Теперь при деплое в default окружение, qbec всегда будет деплоить только в указанный Kubernetes-кластер и в указанный неймспейс, то есть вам больше не придётся переключаться между контекстами и неймспейсами для того чтобы выполнить деплой.
    В случае необоходимости вы всегда можете обновить настройки в этом файле.

    Все ваши окружения описываются в qbec.yaml, и в файле params.libsonnet, где сказано откуда нужно брать для них параметры.

    Читать  Гужевой транспорт 21 века

    Дальше мы видим две директории:

    • components/ — здесь будут храниться все манифесты для нашего приложения, они могут быть описанны как в jsonnet так и обычными yaml-файлами
    • environments/ — здесь мы будем описывать все переменные (параметры) для наших окружений.

    По умолчанию мы имеем два файла:

    • environments/base.libsonnet — он будет содержать общие параметры для всех окружений
    • environments/default.libsonnet — содержит параметры для окружения default

    Давайте откроем environments/base.libsonnet и добавим туда параметры для нашего первого компонента:

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

    Создадим также наш первый компонент 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,
    },
    },
    ],
    },
    },
    ],
    },
    },
    ]

    В данном файле мы описали сразу три Kubernetes-cущности, это: Deployment, Service и Ingress. При желании мы могли бы вынести их в разные компоненты, но на данном этапе нам хватит и одного.

    Синтаксис jsonnet очень похож на обычный json, в принципе обычный json уже является валидным jsonnet, так что первое время вам возможно будет проще воспользоваться онлайн-сервисами вроде yaml2json чтобы сконвертировать привычный вам yaml в json, либо, если ваши компоненты не содержат никаких переменных, то их вполне можно описать в виде обычного yaml.

    При работе с jsonnet очень советую установить вам плагин для вашего редактора

    К примеру для vim есть плагин vim-jsonnet, который включает посветку синтаксиса и автоматически выполняет jesonnet fmt при каждом сохранении (требует наличия установленно jsonnet).

    Всё готово, теперь можем начинать деплой:

    Чтобы посмотреть что у нас получилось, выполним:

    qbec show default

    На выходе вы увидите отрендеренные yaml-манифесты, которые будут применены в кластер default.

    Отлично, теперь применим:

    qbec apply default

    На выходе вы всегда увидите, что будет проделанно в вашем кластере, qbec попросит вас согласиться с изменениями, набрав y вы сможете подтвердить свои намерения.

    Готово теперь наше приложение задеплоено!

    В случае внесения изменений вы всегда сможете выполнить:

    qbec diff default

    чтобы посмотреть как эти изменения отразятся на текущем деплое

    Не забываем закоммитить наши изменения:

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

    5. Пробуем Gitlab-runner с Kubernetes-executor

    До недавного времени я использовал только обычный gitlab-runner на заранее подготовленной машине (LXC-контейнере) с shell- или docker-executor. Изначально мы имели несколько таких раннеров глобально определённых в нашем гитлабе. Они собирали docker-образы для всех проектов.

    Но как показала практика — этот вариант не самый идеальный, как в плане практичности, так и в плане безопасности. Гораздо лучше и идеологически правильней иметь отдельные раннеры задеплоенные для каждого проекта, а то и для каждого окружения.

    К счастью это вовсе не проблема, так как теперь мы будем деплоить gitlab-runner непосредственно как часть нашего проекта прямо в Kubernetes.

    Gitlab предоставляет готовый helm-чарт для деплоя gitlab-runner в Kubernetes. Таким образом всё что вам нужно, это узнать registration token для нашего проекта в Settings —> CI / CD —> Runners и передать его helm:

    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

    Где:

    • https://gitlab.com — адрес вашего Gitlab-сервера.
    • yga8y-jdCusVDn_t4Wxc — registration token для вашего проекта.
    • rbac.create=true — предоставляет раннеру необходимое количество привилегий, чтобы иметь возможность создавать поды для выполнения наших задач с помощью kubernetes-executor.

    Если всё сделанно правильно, вы должны увидеть зарегистрированный раннер в секции Runners, в настройках вашего проекта.

    Скриншот добавленного раннера

    Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

    Вот так просто? — да, так просто! Больше никакой мороки с регистрацией раннеров вручную, с этой минуты раннеры будут создаваться и уничтожаться автоматически.

    6. Деплой Helm-чартов с QBEC

    Так как мы приняли решение считать gitlab-runner частью нашего проекта, настало время описать его в нашем Git-репозитории.

    Мы могли бы описать его как отдельный компонент website, но в дальнейшем мы планируем деплоить разные копии website очень часто, в отличии gitlab-runner, который будет задеплоен всего-лишь один раз на каждый Kubernetes-кластер. Так что давайте инициализируем отдельное приложение для него:

    cd deploy
    qbec init gitlab-runner
    cd gitlab-runner

    На этот раз мы не будем описывать Kubernetes-сущности вручную, а возьмём готовый Helm-чарт. Одним из преимуществ qbec является возможность рендерить Helm-чарты прямо из Git-репозитория.

    Давайте подключим его используя git submodule:

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

    Теперь директория vendor/gitlab-runner содержит у нас репозиторий с чартом для gitlab-runner.

    Подобным образом можно подключать и другие репозитории, например и целиком репозиторий с официальными чартами https://github.com/helm/charts

    Давайте опишем компонент 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,
    }
    )

    Первым аргументом к expandHelmTemplate мы передаём путь к чарту, затем params.values, которые возьмём из параметров окружения, затем идёт объект с

    • nameTemplate — название релиза
    • namespace — неймспейс передаваемый хельму
    • thisFile — обязательный параметр, передающий путь к текущему файлу
    • verbose — показывает команду helm template со всеми аргументами при рендеринге чарта

    Теперь опишем параметры для нашего компонента в environments/base.libsonnet:

    local secrets = import ‘../secrets/base.libsonnet’;

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

    Обратите внимание runnerRegistrationToken мы забираем из внешнего файла secrets/base.libsonnet, давайте создадим его:

    {
    runnerRegistrationToken: ‘yga8y-jdCusVDn_t4Wxc’,
    }

    Проверим, всё ли работает:

    qbec show default

    если всё в порядке, то можем удалить наш ранее, задеплоенный через Helm, релиз:

    helm uninstall gitlab-runner

    и задеплоить его же, но уже через qbec:

    qbec apply default

    7. Знакомство с git-crypt

    На данный момент структура нашей директории для gitlab-runner выглядит так:

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

    Но хранить секреты в Git небезопасно, не так-ли? Так что нам нужно должным образом их зашифровать.

    Обычно ради одной переменной это не всегда имеет смысл. Вы можете передавать секреты в qbec и через переменные окружения вашей CI-системы.
    Но стоит заметить, что бывают и более сложные проекты, которые могут содержать гораздо больше секретов, передавать их все через переменные окружения будет крайне затруднительно.

    Кроме того в таком случае мне не удалось бы рассказать вам о таком замечательном инструменте как git-crypt.

    git-crypt ещё удобен тем, что позволяет сохранить всю историю секретов, а также сравнивать, мерджить и разрешать кофликты так-же как мы привыкли делать это в случае с Git.

    Читать  3D-принтер напечатал огромный дом из бетона в кратчайшие сроки

    Первым делом после установки git-crypt нам нужно сгенерировать ключи для нашего репозитория:

    git crypt init

    Если у вас имеется pgp-ключ, то вы можете сразу добавить себя как collaborator’а для этого проекта:

    git-crypt add-gpg-user kvapss@gmail.com

    Таким образом вы всегда сможете расшифровать этот репозиторий используя свой приватный ключ.

    Если же pgp-ключа у вас нет и не предвидится, то вы можете пойти другим путём и экспортировать ключ проекта:

    git crypt export-key /path/to/keyfile

    Таким образом любой, кто обладает экспортированным keyfile сможет расшифровать ваш репозиторий.

    Настало время настроить наш первый секрет.
    Напомню, мы по прежнему находимся в директории deploy/gitlab-runner/, где у нас имеется директория secrets/, давайте же зашифруем все файлы в ней, для этого создадим файл secrets/.gitattributes с таким содержанием:

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

    Как видно из содержания, все файлы по маске * будут прогоняться через git-crypt, за исключением самого .gitattributes

    Проверить это мы можем запустив:

    git crypt status -e

    На выходе получим список всех файлов в репозитории для которых включенно шифрование

    Вот и всё, теперь мы можем смело закоммитить наши изменения:

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

    Для того чтобы заблокировать репозиторий достаточно выполнить:

    git crypt lock

    и тут же все зашифрованные файлы превратятся в бинарное нечто, прочесть их будет невозможно.
    Чтобы расшифровать репозиторий, выполните:

    git crypt unlock

    8. Создаём toolbox-образ

    Toolbox-образ — это такой образ со всеми инструментами который мы будем использовать для деплоя нашего проекта. Он будет использоваться гитлаб-раннером для выполнения задач деплоя.

    Здесь всё просто, создаём новый dockerfiles/toolbox/Dockerfile с таким содержанием:

    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

    Как вы можете заметить, в этом образе мы устанавливаем все утилиты, которые мы использовали для деплоя нашего приложения. Нам не нужен здесь разве что kubectl, но возможно вы захотите поиграться с ним на этапе настройки пайплайна.

    Также чтобы иметь возможность общаться с Kubernetes и выполнять в него деплой, нам нужно настроить роль для подов генерируемых gitlab-runner’ом.

    Для этого перейдём в директорию с gitlab-runner’ом:

    cd deploy/gitlab-runner

    и добавим новый компонент 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,
    },
    ],
    },
    ]

    Так же опишем новые параметры в environments/base.libsonnet, который теперь выглядит так:

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

    Обратите внимание $.components.rbac.name ссылается на name для компонента rbac

    Давайте проверим что изменилось:

    qbec diff default

    и применим наши изменения в Kubernetes:

    qbec apply default

    Так же не забываем закоммитить наши изменения в 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. Наш первый пайплайн и сборка образов по тэгам

    В корне проекта мы создадим .gitlab-ci.yml с таким содержанием:

    .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

    Обратите внимание, мы используем GIT_SUBMODULE_STRATEGY: normal для тех джоб, где нужно явно инициализировать сабмодули перед выполнением.

    Не забываем закоммитить наши изменения:

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

    Думаю можно смело назвать это версией v0.0.1 и повесить тэг:

    git tag v0.0.1

    Тэги мы будем вешать всякий раз тогда, когда нам потребуется зарелизить новую версию. Тэги в Docker-образах будут привязаны к Git-тэгам. Каждый push с новым тэгом будет инициализировать сборку образов с этим тэгом.

    Выполним git push —tags, и посмотрим на наш первый пайплайн:

    Скриншот первого пайплайна

    Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

    Стоит обратить ваше внимание на то, что сборка по тэгам годится для сборки docker-образов, но не подходит для деплоя приложения в Kubernetes. Так как новые тэги могут назначаться и для старых коммитов, в этом случае инициализация пайплайна для них приведёт к деплою старой версии.

    Чтобы решить эту проблему обычно сборка docker-образов привязывается к тэгам, а деплой приложения к ветке master, в которой захардкожены версии собранных образов. Именно в этом случае вы сможете инициализировать rollback простым revert master-ветки.

    10. Автоматизация деплоя

    Для того чтобы Gitlab-runner мог расшифровать наши секреты, нам понадобится экспортировать ключ репозитория, и добавить его в переменные окружения нашей CI:

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

    полученную строку сохраним в Gitlab, для этого перейдём в настройки нашего проекта:
    Settings —> CI / CD —> Variables

    И создадим новую переменную:

    • Type: File
    • Key: GITCRYPT_KEY
    • Value: <ваша строка>
    • Protected: true (на время обучения можно и false)
    • Masked: true
    • Scope: All environments

    Скриншот добавленной переменной

    Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

    Теперь обновим наш .gitlab-ci.yml добавив в него:

    .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__ —yes

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

    Здесь мы задействовали несколько новых опций для qbec:

    • —root some/app — позволяет определить директорию конкретного приложения
    • —force:k8s-context __incluster__ — это магическая переменная, которая говорит что деплой будет происходить в тотже кластер в котором запущен gtilab-runner. Сделать это необходимо, так как в противном случае qbec будет пытаться найти подходяший Kubernetes-сервер в вашем kubeconfig
    • —yes — просто отключает интерактивный шелл Are you sure? при деплое.

    Не забываем закоммитить наши изменения:

    git add .gitlab-ci.yml
    git commit -m «Automate deploy»

    И после git push мы увидим как наши приложения были задеплоены:

    Скриншот второго пайплайна

    Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

    11. Артефакты и сборка при push в master

    Обычно вышеописанных шагов вполне хватает для сборки и доставки почти любого микросервиса, но мы не хотим вешать тэг каждый раз когда нам понадобится обновить сайт. Поэтому мы пойдём более динамическим путём и настроим деплой по digest в master-ветке.

    Идея проста: теперь образ нашего website будет пересобираться каждый раз при push в master, а после этого автоматически деплоиться в Kubernetes.

    Читать  Gifex — все посты | Пикабу

    Давайте обновим эти две джобы в нашем .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__ —yes —vm:ext-str digest=»$DIGEST»

    Обратите внимание, мы добавили ветку master к refs для джобы build_website и мы теперь используем $CI_COMMIT_REF_NAME вместо $CI_COMMIT_TAG, то есть мы отвязываемся от тэгов в Git и теперь будем пушить образ с названием ветки коммита инициализировашего пайплайн. Стоит заметить, что это также будет работать и с тэгами, что позволит нам сохранять снапшоты сайта с определённой версией в docker-registry.

    Когда имя docker-тэга для новой версии сайта может быть неизменно, мы по прежнему должны описывать изменения для Kubernetes, в противном случае он просто не передеплоит приложение из нового образа, так как не заметит никаких изменений в манифесте деплоймента.

    Опция —vm:ext-str digest=»$DIGEST» для qbec — позволяет передать внешную переменную в jsonnet. Мы хотим чтобы с каждым релизом нашего приложения оно передеплоивалось в кластере. Использовать имя тэга, которое теперь может быть неизменным, мы здесь больше не можем, так как нам нужно завязываться на конкретную версию образа и триггерить деплой при её изменении.

    Здесь нам поможет возможность Kaniko сохранять digest образа в файл (опция —digest-file)
    Затем этот файл мы передадим и прочитаем в момент деплоя.

    Обновим параметры для нашего deploy/website/environments/base.libsonnet который теперь будет выглядить так:

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

    Готово, теперь любой коммит в master инициализиует сборку docker-образа для website, а затем его деплой в Kubernetes.

    Не забываем закоммитить наши изменения:

    git add .
    git commit -m «Configure dynamic build»

    Проверим, после git push мы должны увидеть что-то подобное:

    Скриншот пайплайна для master

    Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

    В принципе нам без надобности передеплоивать gitlab-runner при каждом push, если, конечно, ничего не изменилось в его кофигурации, давайте исправим это в .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__ —yes
    only:
    changes:
    — deploy/gitlab-runner/**/*

    changes позволит следить за изменениями в deploy/gitlab-runner/ и будет тригерить нашу джобу только при наличии таковых

    Не забываем закоммитить наши изменения:

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

    git push, так-то лучше:

    Скриншот обновлённого пайплайна

    Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

    12. Dynamic environments

    Настало время разнообразить наш пайплайн динамическими окружениями.

    Для начала обновим джобу build_website в нашем .gitlab-ci.yml, убрав из неё блок only, что заставит Gitlab тригеррить её при любом коммите в любую ветку:

    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/

    Затем обновим джобу deploy_website, добавим туда блок environment:

    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__ —yes —vm:ext-str digest=»$DIGEST»

    Это позволит Gitlab ассоциировать джобу с prod окружением и выводить правильную ссылку на него.

    Теперь добавим ещё две джобы:

    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__ —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__ —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

    Они будут запускаться при push в любые бренчи кроме master и будут деплоить preview версию сайта.

    Мы видим новую опцию для qbec: —app-tag — она позволяет тэгировать задеплоенные версии приложения и работать только в пределах этого тэга, при создании и уничтожении ресурсов в Kubernetes qbec будет оперировать только ими.
    Таким образом мы можем не создавать отдельный энвайромент под каждый review, а просто переиспользовать один и тот же.

    Здесь мы так же используем qbec apply review, вместо qbec apply default — это как раз тот самый момент когда мы постараемся описать различия для наших окружений (review и default):

    Добавим review окружение в deploy/website/qbec.yaml

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

    Затем обьявим его в 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

    И запишем кастомные параметры для него в 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’,
    },
    },
    }

    Давайте также повнимательнее посмотрим на джобу stop_review, она будет тригериться при удалении брэнча и чтобы gitlab не пытался сделать checkout на неё используется GIT_STRATEGY: none, позже мы клонируем master-ветку и удаляем review через неё.
    Немного заморочно, но более красивого способа я пока не нашёл.
    Альтернативным вариантом может быть деплой каждого review в отельный неймспейс, который всегда можно снести целиком.

    Не забываем закоммитить наши изменения:

    git add .
    git commit -m «Enable automatic review»

    git push, git checkout -b test, git push origin test, проверяем:

    Скриншот созданных environments в Gitlab

    Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

    Всё работает? — отлично, удаляем нашу тестовую ветку: git checkout master, git push origin :test, проверяем что джобы на удаление environment отработали без ошибок.

    Здесь сразу хочется уточнить, что создавать ветки может любой девелопер в проекте, он также может изменить .gitlab-ci.yml файл и получить доступ к секретным переменным.
    По этому настоятельно рекомендуется разрешить их использование только для protected-веток, например в master, либо создать отдельный сет переменных под каждое окружение.

    13. Review Apps

    Review Apps это такая возможность гитлаба, которая позволяет для каждого файла в репозитории добавить кнопку для его быстрого просмотра в задеплоенном окружении.

    Для того чтобы эти кнопки появились, необходимо создать файл .gitlab/route-map.yml и описать в нём все трансформации путей, в нашем случае это будет очень просто:

    # Indices
    — source: /content/(.+?)_index.(md|html)/
    public: ‘1’

    # Pages
    — source: /content/(.+?).(md|html)/
    public: ‘1/’

    Не забываем закоммитить наши изменения:

    git add .gitlab/
    git commit -m «Enable review apps»

    git push, и проверяем:

    Скриншот кнопки Review App

    Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

    Job is done!

    Исходники проекта:

    • на Gitlab: https://gitlab.com/kvaps/docs.example.org
    • на GitHub: https://github.com/kvaps/docs.example.org

    Спасибо за внимание, надеюсь вам порнравилось Пробуем новые инструменты для сборки и автоматизации деплоя в Kubernetes

    Источник

    I heart FeedBurner