Как Dailymotion използва Kubernetes: разгръщане на приложения
Ние в Dailymotion започнахме да използваме Kubernetes в продукция преди 3 години. Но разгръщането на приложения на няколко клъстера е истинско предизвикателство, затова през последните години се стремяхме да подобрим нашите инструменти и работни процеси.
Как започна всичко
Тук ще обясним как разгръщаме нашите приложения на няколко клъстера Kubernetes по целия свят.
За да разгръщаме няколко Kubernetes обекта наведнъж, ние използваме , като всичките ни чартове се съхраняват в едно git хранилище. За да разгръщаме пълен стек приложение от няколко услуги, използваме т.н. обобщаващ чарт. По същество, това е чарт, който декларира зависимости и позволява инициализация на API и неговите услуги с една команда.
Също така написахме малък Python скрипт над Helm, за да правим проверки, да създаваме чартове, да добавяме тайни и да разгръщаме приложения. Всички тези задачи се изпълняват на централна CI платформа с помощта на docker образ.
Нека преминем към същността.
Забележка. Когато четете това, първият кандидат за релийз на Helm 3 вече е обявен. Основната версия съдържа набор от подобрения, предназначени да решат някои проблеми, с които сме се сблъсквали в миналото.
Работен процес за разработка на чартове
За приложенията използваме разклонение и решихме да приложим същия подход и към чартовете.
- Разклонение dev се използва за създаване на чартове, които ще бъдат тествани на развойни клъстери.
- Когато пул реквестът бъде подаден в master, те се проверяват в стейджинг.
- Накрая, създаваме пул реквест, за да предадем промените в разклонение prod и да ги приложим в продукция.
Всяка среда има свое частно хранилище, което съхранява нашите чартове, и ние използваме с много полезни API. По този начин осигуряваме строга изолация между средите и проверка на чартовете в реални условия, преди да ги използваме в продукцията.
Хранилища на чартове в различни среди
Струва си да се отбележи, че когато разработчиците подадат разклонение dev, версията на техния чарт автоматично се изпраща в dev Chartmuseum. По този начин всички разработчици използват едно dev хранилище и е нужно внимателно да посочват версията на своя чарт, за да не използват случайно изменения на други.
Освен това, нашият малък Python скрипт проверява Kubernetes обектите спрямо спецификациите на Kubernetes OpenAPI с помощта на , преди да ги публикува в Chartmuseum.
Общ преглед на работния процес по разработка на чарт
- Настройка на задачите в пайплайна според спецификацията за контрол на качеството (lint, unit-test).
- Изпращане на Docker образ с Python инструменти, които разгръщат нашите приложения.
- Настройка на среда по името на клона.
- Проверка на Kubernetes yaml файлове с Kubeval.
- Автоматично увеличаване на версията на чарта и на родителските чартове (чартове, които зависят от променящия се чарт).
- Изпращане на чарт в Chartmuseum, което съответства на неговата среда
Управление на разликите между клъстери
Федерация на клъстери
Имаше време, когато използвахме , където можеха да се обявяват Kubernetes обекти от една API крайна точка. Но възникнаха проблеми. Например, някои Kubernetes обекти не можеха да бъдат създадени в крайна точка на федерацията, което затрудняваше поддържането на съвместните обекти и други обекти за отделни клъстери.
За да решим проблема, започнахме да управляваме клъстерите независимо, което значително опрости процеса (използвахме първата версия на федерацията; във втората нещо може да е сменило).
Геораспределена платформа
Сега нашата платформа е разпределена в 6 региона — 3 локално и 3 в облака.
Разпределено разгръщане
Глобални стойности на Helm
4 глобални стойности на Helm позволяват определяне на разликите между клъстерите. За всички наши чартове има минимални стойности по подразбиране.
global:
cloud: True
env: staging
region: us-central1
clusterName: staging-us-central1Глобални стойности
Тези стойности помагат за определяне на контекста за нашите приложения и се използват за различни задачи: мониторинг, проследяване, логиране, извършване на външни повиквания, мащабиране и т.н.
- «cloud»: имаме хибридна платформа Kubernetes. Например, нашият API е разгръщан в зони на GCP и в нашите дата центрове.
- «env»: някои стойности могат да се променят за неработни среди. Например, определения за ресурси и конфигурации за автоматично мащабиране.
- «region»: тази информация помага за определяне местоположението на клъстера и може да се използва за определяне на най-близките крайни точки за външни услуги.
- «clusterName»: ако и когато искаме да определим стойност за отделен клъстер.
Ето конкретен пример:
{{
/* Връща репликите на Horizontal Pod Autoscaler за GraphQL*/}}
{{- define "graphql.hpaReplicas" -}}
{{- if eq .Values.global.env "prod" }}
{{- if eq .Values.global.region "europe-west1" }}
minReplicas: 40
{{- else }}
minReplicas: 150
{{- end }}
maxReplicas: 1400
{{- else }}
minReplicas: 4
maxReplicas: 20
{{- end }}
{{- end -}}Пример за Helm шаблон
Тази логика е дефинирана в помощен шаблон, за да не замърсява YAML на Kubernetes.
Обявление на приложението
Нашите инструменти за разгръщане са базирани на няколко YAML файла. По-долу е пример за това как обявяваме услугата и нейната топология на мащабиране (брой реплики) в кластера.
releases:
- foo.world
foo.world: # Име на изданието
services: # Списък на приложенията/проектите на dailymotion
foobar:
chart_name: foo-foobar
repo: git@github.com:dailymotion/foobar
contexts:
prod-europe-west1:
deployments:
- name: foo-bar-baz
replicas: 18
- name: another-deployment
replicas: 3Определение на услугата
Това е схемата на всички стъпки, които определят нашия работен процес за разгръщане. Последната стъпка разгръща приложението едновременно в няколко работни клъстера.
Стъпки за разгръщане в Jenkins
А как са тайните?
Що се отнася до сигурността, ние проследяваме всички тайни от различни места и ги съхраняваме в уникално хранилище в Париж.
Нашите инструменти за разгръщане извличат стойностите на тайните от Vault и, когато дойде време за разгръщане, ги поставят в Helm.
За това определихме съвпадение между тайните в Vault и тайните, които нашите приложения изискват:
тайни:
- secret_id: "stack1-app1-password"
контексти:
- име: "по подразбиране"
vaultPath: "\/kv\/dev\/stack1\/app1\/test"
vaultKey: "парола"
- име: "cluster1"
vaultPath: "\/kv\/dev\/stack1\/app1\/test"
vaultKey: "парола"- Определихме общи правила, които следва да се спазват при записването на тайни в Vault.
- Ако тайната се отнася к определен контекст или клъстер, трябва да се добави конкретен запис. (Тук за контекста cluster1 има собствено значение за тайната stack-app1-password).
- В противен случай се използва стойността по подразбиране.
- За всяка точка в този списък в тайната Kubernetes включва двойка ключ-стойност. Поради това шаблонът на тайната в нашите чарти е много прост.
apiVersion: v1
data:
{{- range $key,$value := .Values.secrets }}
{{ $key }}: {{ $value | b64enc | quote }}
{{ end }}
kind: Secret
metadata:
name: "{{ .Chart.Name }}"
labels:
chartVersion: "{{ .Chart.Version }}"
tillerVersion: "{{ .Capabilities.TillerVersion.SemVer }}"
type: OpaqueПроблеми и ограничения
Работа с множество репозитории
В момента разделяме разработката на чартове и приложения. Това означава, че разработчиците трябва да работят в две git репозитории: едно за приложението, а второ — за определяне на неговото разгръщане в Kubernetes. 2 git репозитории — това са 2 работни процеса, и на начинаещите им е лесно да се объркат.
Управлението на обобщените чартове е трудоемко
Както вече споменахме, обобщените чарти са много удобни за определяне на зависимости и бързо разгръщане на няколко приложения. Но ние използваме --reuse-values, за да избегнем предаването на всички стойности всеки път, когато разгръщаме приложение, което влиза в този обобщен чарт.
В работния процес на непрекъсната доставка имаме само две стойности, които редовно се променят: броят на репликите и етикетът на образа (версията). Други, по-стабилни стойности, се променят ръчно и това е доста сложно. Освен това, една грешка при разгръщането на обобщената диаграма може да доведе до сериозни неуспехи, както сме се уверили на собствен опит.
Актуализиране на няколко конфигурационни файла
Когато разработчик добавя ново приложение, той трябва да промени няколко файла: декларация на приложението, списък на секретите, добавяне на приложението в зависимостите, ако то е включено в обобщената диаграма.
Разрешенията на Jenkins са прекалено разширени в Vault
Сега имаме един , който чете всички секрети от Vault.
Процесът на връщане не е автоматизиран
За връщането е необходимо да се изпълни команда на няколко клъстера, а това носи рискове от грешки. Изпълняваме тази операция ръчно, за да гарантираме, че посочваме правилния идентификатор на версията.
Движим се към GitOps
Нашата цел
Искаме да върнем схемата в репозитория на приложението, което разгръща.
Работният процес ще бъде същият като за разработка. Например, когато клон бъде изпратен в основния, разгръщането ще се стартира автоматично. Основната разлика между този подход и текущия работен процес е, че всичко ще се управлява в git (самото приложение и начинът, по който то се разгръща в Kubernetes).
Има няколко предимства:
- Много по-ясно за разработчика. По-лесно е да се научите да прилагате промените в локалната схемата.
- Определението за разгръщане на услугата може да се посочи там, където е и кодът на услугата.
- Управление на изтриването на обобщените схеми. Услугата ще има своето издание на Helm. Това ще позволи управление на жизнения цикъл на приложението (връщане, ъпгрейд) на най-малко ниво, за да не се засягат други услуги.
- Предимствата на git за управление на схемите: отмяна на промени, журнал на одита и т.н. Ако трябва да отмените промяна в схемата, можете да го направите с помощта на git. Разгръщането се стартира автоматично.
- Можете да помислите за усъвършенстване на работния процес на разработка с помощта на инструменти като Skaffold, с който разработчиците могат да тестват измененията в контекст, близък до продукцията.
Двустепенна миграция
Нашите разработчици използват този работен процес вече 2 години, така че ни е необходима възможно най-безболезнена миграция. Затова решихме да добавим междинен етап по пътя към целта.
Първият етап е прост:
- Запазваме подобна структура за настройка на разгръщане на приложения, но в един обект с името DailymotionRelease.
apiVersion: "v1"
kind: "DailymotionRelease"
metadata:
name: "app1.ns1"
environment: "dev"
branch: "mybranch"
spec:
slack_channel: "#admin"
chart_name: "app1"
scaling:
- context: "dev-us-central1-0"
replicas:
- name: "hermes"
count: 2
- context: "dev-europe-west1-0"
replicas:
- name: "app1-deploy"
count: 2
secrets:
- secret_id: "app1"
contexts:
- name: "default"
vaultPath: "\/kv\/dev\/ns1\/app1\/test"
vaultKey: "password"
- name: "dev-europe-west1-0"
vaultPath: "\/kv\/dev\/ns1\/app1\/test"
vaultKey: "password"- 1 релиз на приложение (без обобщени чартове).
- Чартовете в репозитория git на приложението.
Говорихме с всички разработчици, така че процесът на миграция вече е започнал. Първият етап все още се контролира с помощта на платформата CI. Скоро ще напиша още един пост за втория етап: как преминахме на работен процес GitOps с . Ще разкажа как всичко настроихме и с какви трудности се сблъскахме (няколко репозитория, секрети и т.н.). Следете новините.
Тук се опитахме да опишем напредъка ни в работния процес на разгръщане на приложения през последните години, който ни доведе до мисли за подхода GitOps. Все още не сме постигнали целта и ще информираме за резултатите, но в момента сме убедени, че направихме правилно, когато решихме всичко да опростим и да приближим към навиците на разработчиците.
Източник: habr.com
