Спрабуем новыя прылады для зборкі і аўтаматызацыі дэплою ў Kubernetes.

Спрабуем новыя прылады для зборкі і аўтаматызацыі дэплою ў Kubernetes.

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

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

Я пастараўся выбудаваць падобны працэс з нуля, але цалкам пабудаваны на Gitlab CI і свабодных інструментах, якія я прывык выкарыстоўваць для дэплою прыкладанняў у Kubernetes. Сёння я, нарэшце, раскажу вам пра іх падрабязней.

У артыкуле будуць разгледжаны такія інструменты як:
Х'юга, qbec, каніко, git-crypt и GitLab CI са стварэннем дынамічных акружэнняў.

Змест

  1. Знаёмства з Hugo
  2. Падрыхтоўка Dockerfile
  3. Знаёмства з kaniko
  4. Знаёмства з qbec
  5. Спрабуем Gitlab-runner з Kubernetes-executor
  6. Дэплой Helm-чартаў з qbec
  7. Знаёмства з git-crypt
  8. Ствараем toolbox-выява
  9. Наш першы пайплайн і зборка вобразаў па тэгах
  10. Аўтаматызацыя дэплою
  11. Артэфакты і зборка пры push у master
  12. Dynamic environments
  13. Review Apps

1. Знаёмства з Hugo

У якасці прыкладу нашага праекту мы паспрабуем стварыць сайт для публікацыі дакументацыі, пабудаваны на Hugo. Hugo - гэта статычны генератар кантэнту.

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

Гэта значыць на выхадзе вы атрымаеце структуру дырэкторый і набор згенераваных html-файлаў, якія можна будзе проста заліць на любы танны хостынг і атрымаць працоўны сайт.

Hugo можна ўсталяваць лакальна і паспрабаваць яго ў справе:

Ініцыялізуем новы сайт:

hugo new site docs.example.org

І заадно git-рэпазітар:

cd docs.example.org
git init

Пакуль што наш сайт некранута чысты і каб на ім нешта з'явілася спачатку нам трэба падлучыць тэму, тэма - усяго толькі гэта набор тэмплейтаў і зададзеных правіл па якіх генеруецца наш сайт.

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

Асобная ўвага жадаецца надаць таму, што нам не патрабуецца захоўваць файлы тэмы ў рэпазітары нашага праекту, замест гэтага мы можам проста падлучыць яе выкарыстоўваючы 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 змяшчае два З, гэтая магчымасць называецца multi-stage build і дазваляе выключыць з фінальнай docker-выявы ўсё непатрэбнае.
Такім чынам фінальны вобраз у нас будзе змяшчаць толькі darkhttpd (лёгаважны HTTP-сервер) і public/ — кантэнт нашага статычна згеніраванага сайта.

Не забываем закамаміціць нашы змены:

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

3. Знаёмства з kaniko

У якасці зборшчыка docker-вобразаў я вырашыў выкарыстоўваць каніко, бо для яго працы не патрабуецца наяўнасць 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.
Цяпер пры дэплоі ў дэфолт асяроддзе, qbec заўсёды будзе дэплоіць толькі ў паказаны Kubernetes-кластар і ў паказаны неймспейс, гэта значыць вам больш не прыйдзецца перамыкацца паміж кантэкстамі і неймспейсамі для таго каб выканаць дэплой.
У выпадку неабходнасці вы заўсёды можаце абнавіць наладкі ў гэтым файле.

Усе вашыя асяроддзі апісваюцца ў qbec.yaml, і ў файле params.libsonnet, дзе сказана адкуль трэба браць для іх параметры.

Далей мы бачым дзве дырэкторыі:

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

Па змаўчанні мы маем два файлы:

  • environments/base.libsonnet - ён будзе змяшчаць агульныя параметры для ўсіх акружэнняў
  • environments/default.libsonnet - Змяшчае параметры перавызначаныя для асяроддзя дэфолт

Давайце адкрыем 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-сутнасці, гэта: разгортванне, абслугоўванне и Уваход. Пры жаданні мы маглі б вынесці іх у розныя кампаненты, але на дадзеным этапе нам хопіць і аднаго.

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

Пры працы з jsonnet вельмі раю ўсталяваць вам плягін для вашага рэдактара

Напрыклад для vim ёсць убудова vim-jsonnet, які ўключае падсветку сінтаксісу і аўтаматычна выконвае jsonnet 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. Такім чынам усё што вам трэба, гэта даведацца маркер рэгістрацыі для нашага праекта ў 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.

Калі ўсё зроблена правільна, вы павінны ўбачыць зарэгістраваны раннер у секцыі Бегуны, у наладах вашага праекта.

Скрыншот дабаўленага раннера

Спрабуем новыя прылады для зборкі і аўтаматызацыі дэплою ў Kubernetes.

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

6. Дэплой Helm-чартаў з QBEC

Бо мы прынялі рашэнне лічыць gitlab-runner часткай нашага праекта, надышоў час апісаць яго ў нашым Git-рэпазітары.

Мы маглі б апісаць яго як асобны кампанент сайт, але ў далейшым мы плануем дэплоіць розныя копіі. сайт вельмі часта, у адрозненні 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 - назва рэлізу
  • прастора імёнаў - неймспейс перадаецца Хельма
  • thisFile - абавязковы параметр, які перадае шлях да бягучага файла
  • шматслоўны - паказвае каманду 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

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.

Перш за ўсё пасля ўсталёўкі git-crypt нам трэба згенераваць ключы для нашага рэпазітара:

git crypt init

Калі ў вас маецца PGP-ключ, тыя вы можаце адразу дадаць сябе як collaborator'а для гэтага праекту:

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

Такім чынам вы заўсёды зможаце расшыфраваць гэты рэпазітар выкарыстоўваючы свой прыватны ключ.

Калі ж 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

Як вы можаце заўважыць, у гэтай выяве мы ўсталёўваем усе ўтыліты, якія мы выкарыстоўвалі для дэплою нашага прыкладання. Нам не патрэбен тут хіба што кубектль, але магчыма вы захочаце пагуляцца з ім на этапе налады пайплайна.

Таксама каб мець магчымасць мець зносіны з 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 спасылаецца на імя для кампанента 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-вобразаў прывязваецца да тэгаў, а дэплой прыкладанні да галінкі майстар, у якой захардскураны версіі сабраных вобразаў. Менавіта ў гэтым выпадку вы зможаце ініцыялізаваць rollback простым revert майстар-галінкі.

10. Аўтаматызацыя дэплою

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

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

атрыманы радок захаваем у Gitlab, для гэтага пяройдзем у налады нашага праекта:
Settings -> CI / CD -> Variables

І створым новую зменную:

тып
Ключ
значэнне
абаронены
замаскіраваны
Сфера

File
GITCRYPT_KEY
<your string>
true (на час навучання можна і false)
true
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__ --wait --yes

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

Тут мы задзейнічалі некалькі новых опцый для qbec:

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

Не забываем закамаміціць нашы змены:

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

І пасля мярзотнік штуршок мы ўбачым як нашы прыкладанні былі задэплоены:

Скрыншот другога пайплайну

Спрабуем новыя прылады для зборкі і аўтаматызацыі дэплою ў Kubernetes.

11. Артэфакты і зборка пры push у master

Звычайна вышэйапісаных крокаў суцэль хапае для зборкі і дастаўкі амаль любога мікрасэрвісу, але мы не жадаем вешаць тэг кожны раз калі нам спатрэбіцца абнавіць сайт. Таму мы пойдзем больш дынамічным шляхам і наладзім дэплой па digest у master-галінцы.

Ідэя простая: зараз выява нашага сайт будзе перазбірацца кожны раз пры push у майстар, а пасля гэтага аўтаматычна дэплаіравацца ў Kubernetes.

Давайце абновім гэтыя дзве джобы ў нашым .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"

Звярніце ўвагу, мы дадалі галінку майстар к рефов для джобы 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',
    },
  },
}

Гатова, зараз любы коміт у майстар ініцыялізуе зборку docker-выявы для сайт, А затым яго дэплой у Kubernetes.

Не забываем закамаміціць нашы змены:

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

Праверым, пасля мярзотнік штуршок мы павінны ўбачыць нешта падобнае:

Скрыншот пайплайна для 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__ --wait --yes
  only:
    changes:
      - deploy/gitlab-runner/**/*

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

Не забываем закамаміціць нашы змены:

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

мярзотнік штуршок, вось так лепш:

Скрыншот абноўленага пайплайну

Спрабуем новыя прылады для зборкі і аўтаматызацыі дэплою ў Kubernetes.

12. Dynamic environments

Надышоў час разнастаіць наш пайплайн дынамічнымі асяродкамі.

Для пачатку абновім джобу build_website у нашым .gitlab-ci.yml, прыбраўшы з яе блок толькі, што прымусіць 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, дадамо туды блок навакольнае асяроддзе:

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"

Гэта дазволіць 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__ --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

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

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

Тут мы таксама выкарыстоўваем qbec apply review, замест qbec apply default - гэта як раз той самы момант калі мы пастараемся апісаць адрозненні для нашых акружэнняў (review і default):

Дадамо агляд асяроддзе ў 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, пазней мы клануем майстар-галінку і выдаляем review праз яе.
Трохі замарочна, але прыгажэйшага спосабу я пакуль не знайшоў.
Альтэрнатыўным варыянтам можа быць дэплой кожнага review у гатэльны неймспейс, які заўсёды можна знесці цалкам.

Не забываем закамаміціць нашы змены:

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

мярзотнік штуршок, git checkout -b test, git push origin test, правяраем:

Скрыншот створаных environments у Gitlab

Спрабуем новыя прылады для зборкі і аўтаматызацыі дэплою ў Kubernetes.

Усё працуе? - выдатна, выдаляем нашу тэставую галінку: мярзотнік майстар фатаграфіі, git push origin :test, правяраем што джобы на выдаленне environment адпрацавалі без памылак.

Тут адразу жадаецца ўдакладніць, што ствараць галінкі можа любы дэвелапер у праекце, ён таксама можа змяніць. .gitlab-ci.yml файл і атрымаць доступ да сакрэтных зменных.
Таму настойліва рэкамендуецца дазволіць іх выкарыстанне толькі для protected-галінак, напрыклад у майстар, альбо стварыць асобны сэт зменных пад кожнае асяроддзе.

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"

мярзотнік штуршок, і правяраем:

Скрыншот кнопкі Review App

Спрабуем новыя прылады для зборкі і аўтаматызацыі дэплою ў Kubernetes.

Job is done!

Зыходнікі праекта:

Дзякуй за ўвагу, спадзяюся вам спадабалася Спрабуем новыя прылады для зборкі і аўтаматызацыі дэплою ў Kubernetes.

Крыніца: habr.com

Дадаць каментар