
Прывітанне! За апошні час выйшла шмат класных прылад аўтаматызацыі як для зборкі Docker-вобразаў так і для дэплою ў Kubernetes. У сувязі з гэтым вырашыў пагуляцца з гітлабам, як трэба вывучыць яго магчымасці і, вядома ж, наладзіць пайплайн.
Натхненнем для гэтай працы стаў сайт , Які генеруецца з аўтаматычна, а на кожны дасланы пул рэквест робат аўтаматычна генеруе preview-версію сайта з вашымі зменамі і дае спасылку для прагляду.
Я пастараўся выбудаваць падобны працэс з нуля, але цалкам пабудаваны на Gitlab CI і свабодных інструментах, якія я прывык выкарыстоўваць для дэплою прыкладанняў у Kubernetes. Сёння я, нарэшце, раскажу вам пра іх падрабязней.
У артыкуле будуць разгледжаны такія інструменты як:
Х'юга, qbec, kaniko, git-crypt и GitLab CI са стварэннем дынамічных акружэнняў.
Змест
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І па адрасе праверыць наш толькі што створаны сайт, усе змены, зробленыя ў дырэкторыі аўтаматычна абнаўляюць і адкрытую старонку ў браўзэры, вельмі зручна!
Паспрабуем стварыць тытульную старонку ў content/_index.md:
# My docs site
## Welcome to the docs!
You will be very smart :-)Скрыншот толькі што створанай старонкі

Для генерацыі сайта дастаткова запусціць:
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 змяшчае два З, гэтая магчымасць называецца і дазваляе выключыць з фінальнай 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

4. Знаёмства з 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, дзе сказана адкуль трэба браць для іх параметры.
Далей мы бачым дзве дырэкторыі:
- components/ - тут будуць захоўвацца ўсе маніфесты для нашага прыкладання, яны могуць быць апісаны як у 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 Такім чынам усё што вам трэба, гэта даведацца 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Дзе:
- - адрас вашага Gitlab-сервера.
- yga8y-jdCusVDn_t4Wxc - registration token для вашага праекта.
- rbac.create=true - дае раннеру неабходную колькасць прывілеяў, каб мець магчымасць ствараць поды для выканання нашых задач з дапамогай kubernetes-executor.
Калі ўсё зроблена правільна, вы павінны ўбачыць зарэгістраваны раннер у секцыі Бегуны, у наладах вашага праекта.
Скрыншот дабаўленага раннера

Вось так проста? - так, так проста! Больш ніякай марокі з рэгістрацыяй раннераў ўручную, з гэтай хвіліны раннеры будуць стварацца і знішчацца аўтаматычна.
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.
Падобнай выявай можна падлучаць і іншыя рэпазітары, напрыклад і цалкам рэпазітар з афіцыйнымі чартамі.
Давайце апішам кампанент 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 default7. Знаёмства з 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 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 unlock8. Ствараем 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, і паглядзім на наш першы пайплайн:
Скрыншот першага пайплайну

Варта звярнуць вашу ўвагу на тое, што зборка па тэгах падыходзіць для зборкі 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
Скрыншот дабаўленай зменнай

Цяпер абновім наш .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"І пасля мярзотнік штуршок мы ўбачым як нашы прыкладанні былі задэплоены:
Скрыншот другога пайплайну

11. Артэфакты і зборка пры push у 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

У прынцыпе нам без патрэбы перадаплёваць 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"мярзотнік штуршок, вось так лепш:
Скрыншот абноўленага пайплайну

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

Усё працуе? - Выдатна, выдаляем нашу тэставую галінку: git checkout master, git push origin :test, правяраем што джобы на выдаленне environment адпрацавалі без памылак.
Тут адразу жадаецца ўдакладніць, што ствараць галінкі можа любы дэвелапер у праекце, ён таксама можа змяніць. .gitlab-ci.yml файл і атрымаць доступ да сакрэтных зменных.
Таму настойліва рэкамендуецца дазволіць іх выкарыстанне толькі для protected-галінак, напрыклад у майстар, альбо стварыць асобны сэт зменных пад кожнае асяроддзе.
13. 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

Job is done!
Зыходнікі праекта:
- на Gitlab:
- на GitHub:
Дзякуй за ўвагу, спадзяюся вам спадабалася ![]()
Крыніца: habr.com
