ProHoster > Блог > адміністрування > Пробуємо нові інструменти для збирання та автоматизації деплою в Kubernetes
Пробуємо нові інструменти для збирання та автоматизації деплою в Kubernetes
Вітання! Останнім часом вийшло багато класних інструментів автоматизації як для збирання Docker-образів так і для деплою Kubernetes. У зв'язку з цим вирішив погратися з гітлабом, як слід вивчити його можливості та, звичайно ж, налаштувати пайплайн.
Натхненням для цієї роботи став сайт kubernetes.io, який генерується з вихідних кодів автоматично, а на кожен надісланий пул реквест робот автоматично генерує preview-версію сайту з вашими змінами та надає посилання для перегляду.
Я постарався побудувати подібний процес з нуля, але повністю побудований на Gitlab CI та вільних інструментах, які я звик використати для деплою додатків у Kubernetes. Сьогодні я нарешті розповім вам про них докладніше.
У статті будуть розглянуті такі інструменти як: Хьюго, qbec, каніко, git-crypt и GitLab CI зі створенням динамічних оточень.
Як приклад нашого проекту, ми спробуємо створити сайт для публікації документації, побудований на Hugo. Hugo – це статичний генератор контенту.
Для тих, хто не знайомий зі статичними генераторами, розповім про них трохи докладніше. На відміну від звичайних движків сайтів з базою даних і яким-небудь php, які, за запитом користувача, генерують сторінки на льоту, статичні генератори влаштовані трохи інакше. Вони дозволяють взяти вихідники, як правило це набір файлів в Markdown-розмітці та шаблони тем, потім скомпілювати їх у повністю готовий сайт.
Тобто на виході ви отримаєте структуру директорій та набір згенерованих html-файлів, які можна буде просто залити на будь-який дешевий хостинг та отримати робочий сайт.
Hugo можна встановити локально і спробувати його у справі:
Ініціалізуємо новий сайт:
hugo new site docs.example.org
І заразом git-репозиторій:
cd docs.example.org
git init
Поки що наш сайт незаймано чистий і щоб на ньому щось з'явилося спочатку нам потрібно підключити тему, тема — це лише набір темплейтів і заданих правил за якими генерується наш сайт.
Як тему ми будемо використовувати Вчитисяяка, на мій погляд, якнайкраще підходить для сайту з документацією.
Окрему увагу хочеться приділити тому, що нам не потрібно зберігати файли теми в репозиторії нашого проекту, натомість ми можемо просто підключити її використовуючи підмодуль git:
Таким чином, у нашому репозиторії будуть знаходитися тільки файли, що безпосередньо відносяться до нашого проекту, а підключена тема залишиться у вигляді посилання на конкретний репозиторій і коміт у ньому, тобто її завжди можна буде витягнути з оригінального джерела і не боятися несумісних змін.
Підправимо конфіг 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 :-)
Скріншот щойно створеної сторінки
Для створення сайту достатньо запустити:
hugo
Вміст директорії публічний/ і буде вашим сайтом.
Так, до речі, давайте відразу внесемо її в .gitignore:
echo /public > .gitignore
Не забуваємо закомітити наші зміни:
git add .
git commit -m "New site created"
2. Підготовка Dockerfile
Настав час визначити структуру нашого репозиторію. Зазвичай я використовую щось на зразок:
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-сервер) та публічний/ - Контент нашого статично згенерованого сайту.
Не забуваємо закомітити наші зміни:
git add dockerfiles/website
git commit -m "Add Dockerfile for website"
3. Знайомство з kaniko
Як збирач docker-образів я вирішив використовувати канікоТак як для його роботи не потрібна наявність docker-демона, а саму збірку можна проводити на будь-якій машині і зберігати кеш прямо в registre, позбавляючись, тим самим, необхідності мати повноцінне persistent-сховище.
Для складання образу достатньо запустити контейнер з kaniko executor і передати йому поточний контекст складання, зробити це можна і локально через docker:
Де 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
Qbec — це інструмент деплою, який дозволяє декларативно описувати маніфести вашої програми та деплоїти їх у Kubernetes. Використання Jsonnet як основного синтаксису дозволяє дуже спростити опис відмінностей для кількох оточень, а також майже повністю позбавляє повторюваності коду.
Це може бути особливо актуальним у тих випадках, коли вам потрібно задеплоїти додаток у кілька кластерів з різними параметрами і ви хочете декларативно описати їх у Git.
Qbec також дозволяє рендерити Helm-чарти передаючи їм необхідні параметри і надалі оперувати ними так само як і звичайними маніфестами, у тому числі можна накладати на них різні мутації, а це, у свою чергу, дозволяє позбутися необхідності використовувати ChartMuseum. Тобто можна зберігати та рендерувати чарти прямо з git, де їм і саме місце.
Як я говорив раніше, всі деплойменти ми зберігатимемо в директорії deploy/:
Тут нас цікавить насамперед spec.environments, qbec вже створив за нас default оточення і взяв адресу сервера, а також namespace з нашого поточного kubeconfig.
Тепер при деплої в дефолт оточення, qbec завжди буде деплоїти тільки у вказаний Kubernetes-кластер і в зазначений неймспейс, тобто вам більше не доведеться перемикатися між контекстами та неймспейсами для того, щоб виконати деплой.
У разі потреби ви завжди можете оновити налаштування у цьому файлі.
Всі ваші оточення описуються в qbec.yaml, і у файлі params.libsonnet, де сказано, звідки потрібно брати для них параметри.
Далі ми бачимо дві директорії:
компоненти/ — тут зберігатимуться всі маніфести для нашої програми, вони можуть бути описані як у jsonnet так і звичайними yaml-файлами
середовища/ - тут ми описуватимемо всі змінні (параметри) для наших оточень.
За замовчуванням ми маємо два файли:
environments/base.libsonnet — він міститиме загальні параметри для всіх оточень
environments/default.libsonnet — містить параметри перевизначені для оточення дефолт
Давайте відкриємо environments/base.libsonnet та додамо туди параметри для нашого першого компонента:
У даному файлі ми описали відразу три 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. Спочатку ми мали кілька таких раннерів, глобально визначених у нашому гітлабі. Вони збирали образи для всіх проектів.
Але, як показала практика, цей варіант не найідеальніший, як у плані практичності, так і в плані безпеки. Набагато краще та ідеологічно правильніше мати окремі раннери задеплоєні для кожного проекту, а то й для кожного оточення.
На щастя, це зовсім не проблема, тому що тепер ми будемо деплоїти. gitlab-runner безпосередньо як частина нашого проекту прямо у Kubernetes.
Gitlab надає готовий helm-чарт для деплою gitlab-runner у Kubernetes. Таким чином все, що вам потрібно, це дізнатися реєстраційний маркер для нашого проекту в Settings -> CI / CD -> Runners і передати його helm:
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 небезпечно, чи не так? Тож нам потрібно належним чином їх зашифрувати.
Зазвичай заради однієї змінної це завжди має сенс. Ви можете передавати секрети в qbec та через змінні оточення вашої CI-системи.
Але варто зауважити, що бувають і складніші проекти, які можуть містити набагато більше секретів, передавати їх усі через змінні оточення буде дуже важко.
Крім того, у такому разі мені не вдалося б розповісти вам про такий чудовий інструмент як git-crypt.
git-crypt ще зручний тим, що дозволяє зберегти всю історію секретів, а також порівнювати, мерджити і дозволяти кофлікти так само, як ми звикли робити це у випадку з Git.
Насамперед після встановлення git-crypt нам потрібно згенерувати ключі для нашого репозиторію:
git crypt init
Якщо у вас є PGP-ключ, то ви можете відразу додати себе як collaborator'а для цього проекту:
Таким чином ви завжди зможете розшифрувати цей репозиторій, використовуючи свій приватний ключ.
Якщо ж PGP-ключа у вас немає і не передбачається, то ви можете піти іншим шляхом та експортувати ключ проекту:
git crypt export-key /path/to/keyfile
Таким чином будь-хто, хто володіє експортованим ключовий файл зможе розшифрувати ваш репозиторій.
Настав час налаштувати наш перший секрет.
Нагадаю, ми, як і раніше, знаходимося в директорії deploy/gitlab-runner/, де у нас є директорія secrets/, давайте ж зашифруємо всі файли в ній, для цього створимо файл secrets/.gitattributes з таким змістом:
Як видно зі змісту, всі файли маски * проганятимуться через 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'ом:
Думаю, можна сміливо назвати це версією v0.0.1 та повісити тег:
git tag v0.0.1
Теги ми вішатимемо щоразу тоді, коли нам буде потрібно зарелізувати нову версію. Теги в Docker-образах будуть прив'язані до Git-тегів. Кожен push з новим тегом ініціалізуватиме складання образів з цим тегом.
Виконаємо git push -tags, і подивимося на наш перший пайплайн:
Скріншот першого пайплайну
Варто звернути вашу увагу на те, що складання по тегах годиться для складання docker-образів, але не підходить для деплою програми в Kubernetes. Оскільки нові теги можуть бути призначені і для старих коммітів, у цьому випадку ініціалізація пайплайну для них призведе до деплою старої версії.
Щоб вирішити цю проблему зазвичай збирання docker-образів прив'язується до тегів, а деплой додатки до гілки майстер, в якій захардшкірено версії зібраних образів. Саме в цьому випадку ви зможете ініціалізувати rollback простим revert майстер-Гілки.
10. Автоматизація деплою
Для того, щоб Gitlab-runner міг розшифрувати наші секрети, нам знадобиться експортувати ключ репозиторію і додати його в змінні оточення нашої CI:
-root some/app — дозволяє визначити директорію конкретної програми
-force:k8s-context __incluster__ - це магічна змінна, яка говорить, що деплой відбуватиметься в той же кластер, в якому запущений gtilab-runner. Зробити це необхідно, тому що в іншому випадку qbec намагатиметься знайти підходящий Kubernetes-сервер у вашому kubeconfig
-wait - Примушує qbec дочекатися, коли створювані ним ресурси перейдуть у стан Ready і тільки потім завершиться з успішним exit-code.
-yes - просто відключає інтерактивний шелл Ти впевнений? при депло.
І після git push ми побачимо як наші програми були задеплоєні:
Скріншот другого пайплайну
11. Артефакти та складання при push у master
Зазвичай вищеописаних кроків цілком вистачає для складання та доставки майже будь-якого мікросервісу, але ми не хочемо вішати тег щоразу, коли нам знадобиться оновити сайт. Тому ми підемо динамічнішим шляхом і налаштуємо деплою по digest у master-гілці.
Ідея проста: тепер образ нашого сайт буде перезбиратися кожного разу у push у майстер, а після цього автоматично деплоїться у Kubernetes.
Давайте оновимо ці дві джоби у нашому .gitlab-ci.yml:
Зверніть увагу, ми додали гілку майстер к рек для джоби 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 який тепер виглядатиме так:
Готово, тепер будь-який коміт у майстер ініціалізує складання docker-образу для сайт, а потім його деплою в Kubernetes.
Не забуваємо закомітити наші зміни:
git add .
git commit -m "Configure dynamic build"
Перевіримо, після git push ми маємо побачити щось подібне:
Скріншот пайплайну для master
В принципі нам без потреби передеплоювати gitlab-runner при кожному push, якщо, звичайно, нічого не змінилося в його кофігурації, давайте виправимо це в .gitlab-ci.yml:
Настав час урізноманітнити наш пайплайн динамічними оточеннями.
Для початку оновимо джобу build_website в нашому .gitlab-ci.ymlприбравши з неї блок тільки, що змусить Gitlab тригерити її при будь-якому коміті в будь-яку гілку:
Вони будуть запускатися у push у будь-які бренчі крім master та будуть деплоїти preview версію сайту.
Ми бачимо нову опцію для qbec: -app-tag — вона дозволяє тегувати задеплоєні версії програми та працювати тільки в межах цього тегу, при створенні та знищенні ресурсів у Kubernetes qbec оперуватиме лише ними.
Таким чином, ми можемо не створювати окремий енвайромент під кожен review, а просто перевикористовувати один і той же.
Тут ми так само використовуємо qbec apply review, замість qbec apply default — це саме той момент, коли ми спробуємо описати відмінності для наших оточень (review і default):
Потім оголосимо його в 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 push origin :test, перевіряємо, що джоби на видалення environment відпрацювали без помилок.
Тут відразу хочеться уточнити, що створювати гілки може будь-який девелопер у проекті, він також може змінити .gitlab-ci.yml файл та отримати доступ до секретних змінних.
Тому рекомендується дозволити їх використання тільки для protected-гілок, наприклад в майстерабо створити окремий сет змінних під кожне оточення.
13. Review Apps
Огляд додатків це така можливість гітлабу, яка дозволяє кожному файлу в репозиторії додати кнопку для його швидкого перегляду в задеплоенном оточенні.
Для того, щоб ці кнопки з'явилися, необхідно створити файл .gitlab/route-map.yml і описати у ньому всі трансформації шляхів, у разі це буде дуже просто: