Как 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 инструменти, които разгръщат нашите приложения.
- Настройка на средата по име на клона.
- Проверка на YAML файлове на Kubernetes с 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
Нашата цел
Искаме да върнем чарт в хранилището на приложението, което той разгръща.
Работният процес ще бъде същият като за разработка. Например, когато клонът бъде изпратен в master, разгръщането ще бъде стартирано автоматично. Основната разлика между този подход и текущия работен процес ще бъде в това, че всичко ще се управлява в 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 релиз на приложение (без обобщени чартове).
- Чартовете в репозитория на приложението.
Говорихме с всички разработчици, така че процесът на миграция вече е започнал. Първата стъпка все още се контролира с помощта на платформата CI. Скоро ще напиша още един пост за втората стъпка: как преминахме към работен процес с GitOps с . Ще разкажа как всичко настроихме и с какви трудности се сблъскахме (няколко репозитория, секрети и т.н.). Следете новините.
Тук опитахме да опишем напредъка си в работния процес на разгръщане на приложения през последните години, който ни доведе до разсъждения за подхода GitOps. Все още не сме постигнали целта и ще докладваме за резултатите, но в момента сме убедени, че направихме правилно, когато решихме да опростим всичко и да го приближим до навиците на разработчиците.
Източник: habr.com
