Привет, Хабр! Меня зовут Егор Комаров, я тестировщик в команде #CloudMTS.
Сегодня я расскажу, как настроить процесс развертывания и обновления микросервисного приложения от разработчика до облака в две команды.
Когда в приложении появляется новый функционал (например, изменился ответ от сервера), запускается ряд стандартных действий:
- получить фичу от разработчика;
- сбилдить контейнер с новым приложением;
- загрузить контейнер в репозиторий;
- изменить и применить манифест кубера.
Эти рутинные действия можно автоматизировать через функционал gitlab ci.
Архитектура приложения
Рассмотрим по шагам процесс доставки приложения в Kubernetes.
Я создал репозиторий на гитлаб и взял токен для гитлаб-раннера:
Токен вставлю чуть дальше
Хелм — это пакетный менеджер для кубернетиса (как pip для питона).
Для юниксов установка стандартная, для винды ситуация интереснее: скачайте Experimental Windows AMD64 по ссылке.
helm repo add gitlab https://charts.gitlab.io
helm repo update
Чтобы соединить гитлаб ui и гитлаб-раннер в кластере кубера,
я прописываю registration token из пункта выше в конфиг гитлаб-раннера из хелма:
# выведу содержимое конфига в .yml файл
helm show values gitlab/gitlab-runner > murr-gitlab-runner.yml
В murr-gitlab-runner.yml меня интересует три поля:
tags: "murr_runner"
Прописываем свой тег, чтобы идентифицировать гитлаб-раннер. Именно по тегу gitlab поймет, на какой под слать запросы:
gitlabUrl: https://gitlab.com/
Проверяем путь к гитлабу (гитлаб можно установить свой).
runnerRegistrationToken: "___"
Прописываем токен из шага выше.
Создам кластер кубернетиса в облачном сервисе.
Этот сервис #CloudMTS предоставляет набор готовых решений, в частности поднимет мне ноду в кубере, выделит стабильный IP-адрес и предоставит бесплатный плагин в виде ingress-nginx (пустит трафик из интернета в кластер).
На выходе я скачаю кубконфиг.
Переименую в config и закину в C:\Users\Admin\.kube
Проверю доступность кластера.
kubectl get nodes
NAME STATUS ROLES AGE VERSION
liberal-dove-dcddd8-115d1b Ready <none> 84m v1.21.11
Устанавливаю раннер в отдельный неймспейс кубера.
Неймспейс — это как виртуальное окружение в питоне, то есть изолированная среда, в которой можно систематизировать сервисы:
kubectl create ns gitlab-runner
helm install --namespace gitlab-runner gitlab-runner -f murr-gitlab-runner.yml gitlab/gitlab-runner
Выдам полные права раннеру:
kubectl create clusterrolebinding --clusterrole=cluster-admin -n gitlab-runner --serviceaccount=gitlab-runner:default our-murr-runner
Подожду пару минут и проверю, что раннер запущен:
kubectl get po -n gitlab-runner -w
NAME READY STATUS RESTARTS AGE
gitlab-runner-gitlab-runner-994b96676-bjftj 1/1 Running 0 3m33s
Также увижу раннер в настройках гитлаба:
Создам go сервер:
package main
import (
"fmt"
"github.com/rs/cors"
"net/http"
)
func main() {
fmt.Println("murr_server запущен")
mux := http.NewServeMux()
mux.HandleFunc("/murrengan/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"message\": \"Привет, муррен!\"}"))
fmt.Println("Вызвана функция по роуту murrengan")
})
handler := cors.Default().Handler(mux)
err := http.ListenAndServe(":1991", handler)
if err != nil {
fmt.Println("murr_server упал:", err)
}
}
Он возвращает json {“message”: “Привет, муррен!”} при гет-запросе с любого IP на :1991/murrengan/
Проверяем локально:
go run main.go
murr_serve запущен
Теперь по адресу 127.0.0.1:1991/murrengan/ доступно приложение. Откроем его в браузере.
Или проверим в терминале:
curl http://127.0.0.1:1991/murrengan/
{"message": "Привет, муррен!"}
Завернем наше приложение в Dockerfile и добавим возможность запуска в контейнере:
FROM golang:1.17-alpine
WORKDIR /
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY *.go ./
RUN go build -o /murr_server
EXPOSE 1991
ENTRYPOINT ["/murr_server"]
Сбилдим имидж:
docker build -t murr_server_in_docker:0.3.0 .
Проверим готовый имидж:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
murr_server_in_docker 0.3.0 18965967f809 About a minute ago 308MB
Можно запустить и протестировать локально:
docker run -it -p 1991:1991 murr_server_in_docker:0.3.0
Описываем инструкцию работы gitlab runner в .gitlab-ci.yml
# В работе 2 стадии
stages:
- build
- deploy
# https://github.com/GoogleContainerTools/kaniko
# В этой функции мы даем право канико работать с нашим гитлабом.
# Канико позволяет билдить контейнеры в контейнерах.
.docker-login.: &docker-login
before_script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
Build container:
image: gcr.io/kaniko-project/executor:debug
stage: build
<<: *docker-login
# тег указывали в murr-gitlab-runner.yml
tags:
- murr_runner
only:
- new_prod
script:
# тут канико билдит контейнер и через --destination пушит образ в реджистери
# каждый пуш уникальный из-за $CI_COMMIT_SHORT_SHA (глобальная переменная гитлаб-раннера)
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
Deploy container:
image:
# этот образ позволяет запускать kubectl в script
name: lachlanevenson/k8s-kubectl:latest
entrypoint: ["/bin/sh", "-c"]
stage: deploy
tags:
- murr_runner
only:
- new_prod
script:
# в manifest.yaml я указал шаблон image: registry.gitlab.com/murrengan/murr_server:change_thist_tag_on_gitlab_ci
# и теперь через утилиту sed меняю change_thist_tag_on_gitlab_ci на уникальный коммит
- sed -i "s|change_thist_tag_on_gitlab_ci|${CI_COMMIT_SHORT_SHA}|" manifest.yaml
# применяю новый деплоймент
- kubectl apply -n default -f manifest.yaml
Отдельно можно отметить manifest.yaml. Манифест — это описание состояния кластера кубернетис. Ты пишешь, как хочешь, чтобы было, а кубер старается так сделать.
apiVersion: apps/v1
kind: Deployment
metadata:
name: murr-server-deployment
spec:
selector:
matchLabels:
app: murr-server
replicas: 2
template:
metadata:
labels:
app: murr-server
spec:
containers:
- name: murr-server
image: registry.gitlab.com/murrengan/murr_server:change_thist_tag_on_gitlab_ci
imagePullPolicy: Always
ports:
- containerPort: 1991
---
kind: Service
apiVersion: v1
metadata:
name: murr-server-service
spec:
selector:
app: murr-server
ports:
- port: 1991
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: murr-server-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- http:
paths:
- path: /murr_server(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: murr-server-service
port:
number: 1991
Деплоймент следит за количеством экземпляров приложения мурр_сервер, версией и открытыми портами. Когда я обновляю мурр_сервер, именно деплоймент будет терминейтить старые поды и включать новые.
Сервис указывает, какое приложение на каком порту ждет трафик.
И как раз в ингрессе я через предустановленный плагин получаю трафик и проксирую его на url /murr_server — получается, чтобы обратиться к приложению, url будет выглядеть так:
http://EXTERNAL-IP_от_провайдера_/murr_server/murrengan/
Пушим наши изменения в продакшен-ветку — new_prod:
git add .
git commit
git push --set-upstream origin new_prod
Идем в пайплайны и видим запуск:
Ждем окончания второй джобы:
Ждем запуск сервиса для доступа к приложению:
kubectl get po -w
NAME READY STATUS RESTARTS AGE
murr-server-deployment-748f76bbb8-2hxxn 1/1 Running 0 60s
murr-server-deployment-748f76bbb8-77ldv 1/1 Running 0 60s
Нам надо получить EXTERNAL-IP для murr-server-load-balancer. Теперь, указав его в браузере, мы получим доступ к нашему приложению из любой точки мира.
В данном примере url выглядит так:
http://91.185.95.26/murr_server/murrengan/
Теперь приложение доступно во всем мире 24/7.
Когда потребуется обновить приложение, достаточно сделать новый коммит и запушить изменения.
Гитлаб сиай подхватит измения, сбилдит новый имидж в раннере у нас в кубере, зальет его в реджестери, обновит и применит новый деплоймент.
Впереди много задач: линтер, тестирование, фронтенд, https и murr_game…
Спасибо за ваш интерес к теме. Пишите в комментариях, если у вас есть вопросы.
Комментарии (7)
past
01.04.2022 12:39+1Посмотрите, как работает ArgoCD/Flux. kubectl apply из CI это всё же не совсем gitops
shep
01.04.2022 22:11сбилдить контейнер с новым приложением;
загрузить контейнер в репозиторий;
Образ, пожалуйста, вы билдите и пушите образ...
Ну замечание по мультистейдж билд.
Теги образа в прод (only: [new_prod]) хэшем коммита.
Конструкция с printf при формировании basic auth - восторг. В той же доке гитлаба более лаконичный пример.
Какой-то анти деврел у вас. Неужели статьи перед публикацией не вычитываются хоть кем-то?
Owleyeinnose
02.04.2022 17:33то чувство, когда самые скостыленные и непричесанные пайпы в компании выглядят лучше статьи на хабре... ужасное чувство если честно.
gohrytt
Сейчас бы запускать программу в том же контейнере где она собиралась... Чисто +300 мегабайт к весу образа.
AlexGluck
Но но но, не собирать же бинарник статически, чтобы он прям был distroless и весил мегабайта 4. Вдруг всё станет работать быстрее и надёжнее, мы этого не хотим.
past
Именно так, статический в distroless