Давным-давно на работе мне поручили переключить олдскульный стек LAMP на Kubernetes. Мой начальник в то время всегда гонялся за новыми технологиями, думая, что на итерацию старых и новых технологий уйдет всего несколько дней, а поскольку в то время мы даже не знали, как работают контейнеры, я должен сказать, что босс идея была верной очень смелой.
Прочитав официальную документацию и много поискав, мы начали чувствовать себя ошеломленными — есть много новых концепций, которые нужно изучить: поды, контейнеры, реплики и т. д. Мне кажется, что Kubernetes создан только для группы умных разработчиков.
Затем я сделал то, что обычно делаю в этой ситуации: учился на практике. Сложности хорошо понятны на простом примере, поэтому я сам прошел развертывание шаг за шагом.
В итоге мы это сделали, хоть и далеко от отведенной недели — на создание трех кластеров, включая их dev, test и production, у нас ушел почти месяц.
В этой статье я подробно расскажу, как развернуть приложение в Kubernetes. После прочтения этой статьи у вас будет эффективное развертывание Kubernetes и рабочий процесс непрерывной доставки.
Непрерывная интеграция и доставка
Непрерывная интеграция — это практика создания и тестирования каждого обновления приложения. Выполняя небольшой объем работы, ошибки обнаруживаются раньше и немедленно устраняются.
После завершения интеграции и прохождения всех тестов мы можем добавить непрерывную доставку в процесс автоматизации выпуска и развертывания. Проекты, использующие CI/CD, могут выпускаться чаще и надежнее.
Мы будем использовать Semaphore, быструю, мощную и простую в использовании платформу непрерывной интеграции и доставки (CI/CD), которая автоматизирует все процессы:
1. Установите зависимости проекта
2. Запустите модульные тесты
3. Создайте образ Docker
4. Отправьте образ в Docker Hub.
5. Развертывание Kubernetes в один клик
Для приложения у нас есть микросервис Ruby Sinatra, который предоставляет некоторые конечные точки HTTP. В проекте уже есть все необходимое для развертывания, но некоторые компоненты все еще необходимы.
Готов к работе
Прежде чем начать, вам необходимо войти в свои учетные записи Github и Semaphore. Кроме того, чтобы облегчить извлечение или отправку образов Docker позже, вам необходимо войти в Docker Hub.
Далее вам нужно установить некоторые инструменты на свой компьютер:
- Git: работа с кодом
- curl: швейцарский армейский нож сетевого взаимодействия
- kubectl: удаленно управляйте своим кластером
Конечно, не забывайте о Kubernetes. Большинство облачных провайдеров предлагают эту услугу в различных формах, просто выберите ту, которая соответствует вашим потребностям. Минимальная конфигурация компьютера и размер кластера достаточны для запуска нашего примерного приложения. Мне нравится начинать с кластера из 3 узлов, но вы можете использовать кластер из 1 узла.
Когда кластер будет готов, загрузите файл kubeconfig у своего провайдера. Некоторые позволяют загружать прямо из веб-консоли, другим требуются вспомогательные программы. Этот файл нужен нам для подключения к кластеру.
С этого мы уже можем начать. Первое, что нужно сделать, это разветвить репозиторий.
Форк-репозиторий
Разветвите демонстрационное приложение, которое мы будем использовать в этом посте.
- Посетите репозиторий semaphore-demo-ruby-kubernetes и щелкните в правом верхнем углу.Forkкнопка
- нажмитеClone or downloadкнопку и скопируйте адрес
- Скопируйте репозиторий: $ git clone https://github.com/ваш_репозиторий_путь…
Подключить новый репозиторий с помощью Semaphore
1. Войдите в свой семафор
2. Щелкните ссылку на боковой панели, чтобы создать новый проект.
3. Нажмите рядом с вашим репозиторием [Add Repository] кнопка
Тест с семафором
Непрерывная интеграция делает тестирование увлекательным и эффективным. Хорошо налаженный конвейер непрерывной интеграции может создать быструю петлю обратной связи для выявления ошибок до того, как они нанесут какой-либо ущерб. Наш проект поставляется с готовыми тестами.
Откройте исходный файл конвейера, расположенный по адресу .semaphore/semaphore.yml, и быстро просмотрите его. Этот конвейер описывает все шаги, которые Semaphore должен выполнить для создания и тестирования приложения. Он начинается с версии и имени.
version: v1.0
name: CI
Далее идет агент, представляющий собой виртуальную машину, на которой выполняется задание. Мы можем выбрать из 3 типов:
agent:
machine:
type: e1-standard-2
os_image: ubuntu1804
Блоки, задачи и задания определяют операции, которые должны выполняться на каждом этапе конвейера. В Semaphore блоки выполняются последовательно, и в то же время задания в блоках выполняются параллельно. Конвейер содержит 2 блока: один для установки библиотеки и один для запуска тестов.
Первый блок загружает и устанавливает драгоценные камни Ruby.
- name: Install dependencies
task:
jobs:
- name: bundle install
commands:
- checkout
- cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-$SEMAPHORE_GIT_BRANCH,gems-master
- bundle install --deployment --path .bundle
- cache store gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock) .bundle
Checkout скопировал код с Github. Поскольку каждое задание выполняется на полностью изолированной машине, мы должны полагаться на кеш для хранения и извлечения файлов между запусками задания.
blocks:
- name: Install dependencies
task:
jobs:
- name: bundle install
commands:
- checkout
- cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-$SEMAPHORE_GIT_BRANCH,gems-master
- bundle install --deployment --path .bundle
- cache store gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock) .bundle
Тестируется второй блок. Обратите внимание, что мы повторно использовали код извлечения и кэширования, чтобы поместить исходный файл в задание. Последняя команда используется для запуска набора тестов RSpec.
- name: Tests
task:
jobs:
- name: rspec
commands:
- checkout
- cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-$SEMAPHORE_GIT_BRANCH,gems-master
- bundle install --deployment --path .bundle
- bundle exec rspec
В последней части мы рассмотрим продвижение. Продвижение может соединять конвейеры при определенных условиях для создания сложных рабочих процессов. После того, как все задания выполнены, мы используем auto_promote_on для запуска следующего конвейера.
promotions:
- name: Dockerize
pipeline_file: docker-build.yml
auto_promote_on:
- result: passed
Рабочий процесс продолжается со следующим конвейером.
Создайте образ Docker
Мы можем запустить что угодно в Kubernetes, если оно упаковано в образ Docker. В этой части мы научимся создавать образ.
Наш образ Docker будет содержать код приложения, Ruby и все библиотеки. Давайте сначала посмотрим на Dockerfile:
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y build-essential
ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile* $APP_HOME/
RUN bundle install --without development test
ADD . $APP_HOME
EXPOSE 4567
CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "4567"]
DockerFile похож на подробный рецепт, содержащий все шаги и команды, необходимые для создания изображения контейнера:
1. Начните с готового образа ruby
2. Установите инструменты сборки с помощью apt-get
3. Скопируйте Gemfile, так как в нем есть все зависимости
4. Установите их с помощью пакетов
5. Скопируйте исходный код приложения
6. Определите порт прослушивания и команду запуска
Мы запечем наш рабочий образ в среде Semaphore. Однако, если вы хотите провести быстрый тест на своем компьютере, введите:
$ docker build . -t test-image
Запустите с Docker и откройте внутренний порт 4567 для локального запуска сервера:
$ docker run -p 4567:4567 test-image
Теперь вы можете протестировать доступную конечную точку HTTP:
$ curl -w "\n" localhost:4567
hello world :))
Добавьте учетную запись Docker Hub в Semaphore
Semaphore имеет безопасный механизм для хранения конфиденциальной информации, такой как пароли, токены или ключи. Чтобы иметь возможность отправлять изображения в реестр Docker Hub, вам необходимо создать секрет с вашим именем пользователя и паролем:
- Откройте свой семафор
- На левой панели навигации нажмите [Secret】
- Нажмите【Creat New Secret】
- Имя секрета должно быть Dockerhub, введите данные для входа (как показано на изображении ниже) и сохраните их.
Создание конвейера Docker
Конвейер начинает сборку и отправляет образ в Docker Hub, у него всего 1 блок и 1 задание:
На этот раз нам нужно использовать более высокую производительность, поскольку Docker имеет тенденцию быть более ресурсоемким. Мы выбираем машину среднего уровня e1-standard-4 с четырьмя процессорами, 8 ГБ ОЗУ и 35 ГБ дискового пространства:
version: v1.0
name: Docker build
agent:
machine:
type: e1-standard-4
os_image: ubuntu1804
Блок сборки запускается при входе в Docker Hub, а имя пользователя и пароль можно импортировать из только что созданного нами секрета. После входа в систему Docker может получить прямой доступ к репозиторию образов.
Следующая команда — docker pull, которая пытается получить последний образ. Если образ найден, Docker может повторно использовать некоторые из этих слоев, чтобы ускорить процесс сборки. Не беспокойтесь, если у вас нет последней версии образа, просто сборка займет немного больше времени.
Наконец, мы нажимаем новое изображение. Обратите внимание, что здесь мы используем переменную SEMAPHORE_WORKFLOW_ID, чтобы пометить зеркало.
blocks:
- name: Build
task:
secrets:
- name: dockerhub
jobs:
- name: Docker build
commands:
- echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
- checkout
- docker pull "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest || true
- docker build --cache-from "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest -t "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID .
- docker images
- docker push "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID
Когда изображение готово, мы вступаем в фазу сдачи проекта. Мы расширим наш конвейер Semaphore за счет ручного продвижения.
promotions:
- name: Deploy to Kubernetes
pipeline_file: deploy-k8s.yml
Чтобы выполнить первую автоматическую сборку, выполните push:
$ touch test-build
$ git add test-build
$ git commit -m "initial run on Semaphore“
$ git push origin master
Как только образ будет готов, мы можем перейти к этапу развертывания.
Развертывание в Kubernetes
Автоматическое развертывание — сильная сторона Kubernetes. Что нам нужно сделать, так это сообщить кластеру наши окончательные ожидания, а за все остальное он будет нести ответственность.
Однако перед развертыванием необходимо загрузить файл kubeconfig в Semaphore.
Загрузить Kubeconfig в Semaphore
Нам нужен второй секрет: kubeconfig кластера. Этот файл предоставляет административный доступ к нему. Поэтому мы не хотим проверять файлы в репозиторий.
Создайте секрет с именем do-k8s и загрузите файл kubeconfig в /home/semaphore/.kube/dok8s.yaml:
Контрольный список развертывания
Хотя Kubernetes уже является платформой для оркестровки контейнеров, мы не управляем контейнерами напрямую. Фактически, самая маленькая единица развертывания — это модуль. Стая похожа на группу неразлучных друзей, всегда вместе идущих в одно и то же место. Поэтому убедитесь, что контейнеры в модуле работают на одном узле и имеют один и тот же IP-адрес. Их можно запускать и останавливать синхронно, а поскольку они работают на одной машине, они могут совместно использовать ресурсы.
Проблема с модулями заключается в том, что их можно запускать и останавливать в любое время, и у нас нет возможности узнать, какому IP-адресу модуля они будут назначены. Чтобы перенаправить http-трафик пользователя, вам также необходимо предоставить общедоступный IP-адрес и балансировщик нагрузки, который отвечает за отслеживание модулей и перенаправление клиентского трафика.
Откройте файл, расположенный по адресу deploymente.yml. Вот манифест для развертывания нашего приложения, которое разделено на два ресурса тремя черточками. Сначала разверните ресурсы:
apiVersion: apps/v1
kind: Deployment
metadata:
name: semaphore-demo-ruby-kubernetes
spec:
replicas: 1
selector:
matchLabels:
app: semaphore-demo-ruby-kubernetes
template:
metadata:
labels:
app: semaphore-demo-ruby-kubernetes
spec:
containers:
- name: semaphore-demo-ruby-kubernetes
image: $DOCKER_USERNAME/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID
Вот несколько понятий для пояснения:
- Ресурсы имеют имя и несколько меток для удобства организации
- Спецификация определяет окончательное желаемое состояние, а шаблон — это модель, используемая для создания пода.
- Реплика задает количество создаваемых реплик пода. Мы часто устанавливаем это количество узлов в кластере. Поскольку мы используем 3 узла, я изменил эту командную строку на реплики: 3
Второй ресурс — сервис. Он привязывается к порту 80 и перенаправляет HTTP-трафик на модули в развертывании:
---
apiVersion: v1
kind: Service
metadata:
name: semaphore-demo-ruby-kubernetes-lb
spec:
selector:
app: semaphore-demo-ruby-kubernetes
type: LoadBalancer
ports:
- port: 80
targetPort: 4567
Тег Kubernetes соответствует селектору для подключения к сервисному модулю. Итак, у нас есть кластер в том же количестве сервисов, и мы развертываем и подключаем их по мере необходимости.
Конвейер развертывания
Сейчас мы переходим к заключительному этапу настройки CI/CD. На данный момент у нас есть конвейер CI, определенный в semaphore.yml, и конвейер Docker, определенный в docker-build.yml. На этом этапе мы выполним развертывание в Kubernetes.
Откройте конвейер развертывания в .semaphore/deploy-k8s.yml:
version: v1.0
name: Deploy to Kubernetes
agent:
machine:
type: e1-standard-2
os_image: ubuntu1804
Два задания образуют конечный конвейер:
Задание 1 начинает развертываться. После импорта файла kubeconfig envsubst заменяет переменные-заполнители в deployment.yaml их фактическими значениями. Затем kubectl apply отправляет манифест в кластер.
blocks:
- name: Deploy to Kubernetes
task:
secrets:
- name: do-k8s
- name: dockerhub
env_vars:
- name: KUBECONFIG
value: /home/semaphore/.kube/dok8s.yaml
jobs:
- name: Deploy
commands:
- checkout
- kubectl get nodes
- kubectl get pods
- envsubst < deployment.yml | tee deployment.yml
- kubectl apply -f deployment.yml
Задание 2 помечает образ как актуальный, чтобы мы могли использовать его в качестве кэша при следующем запуске.
- name: Tag latest release
task:
secrets:
- name: dockerhub
jobs:
- name: docker tag latest
commands:
- echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
- docker pull "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID
- docker tag "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest
- docker push "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest
Это последний шаг в рабочем процессе.
Развернуть приложение
Давайте научим наше приложение Sinatra петь. Добавьте следующий код в класс App в app.rb:
get "/sing" do
"And now, the end is near
And so I face the final curtain..."
end
Отправьте измененный файл на Github:
$ git add .semaphore/*
$ git add deployment.yml
$ git add app.rb
$ git commit -m "test deployment”
$ git push origin master
Подождите, пока конвейер сборки докера завершится, и вы сможете просмотреть ход работы Semaphore:
Пришло время развернуть, нажмитеPromoteкнопку, чтобы проверить, работает ли она:
У нас хорошее начало, теперь пришло время взглянуть на Kubernetes. Мы можем проверить статус развертывания с помощью kubectl, начальное состояние — три обязательных модуля и ноль доступных:
$ kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
semaphore-demo-ruby-kubernetes 3 0 0 0 15m
Через несколько секунд pod запустился и согласование завершилось:
$ kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
semaphore-demo-ruby-kubernetes 3 3 3 3 15m
Используйте get all, чтобы получить общее состояние кластера, в котором показаны модули, службы, развертывания и реплики:
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/semaphore-demo-ruby-kubernetes-7d985f8b7c-454dh 1/1 Running 0 2m
pod/semaphore-demo-ruby-kubernetes-7d985f8b7c-4pdqp 1/1 Running 0 119s
pod/semaphore-demo-ruby-kubernetes-7d985f8b7c-9wsgk 1/1 Running 0 2m34s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.12.0.1 443/TCP 24m
service/semaphore-demo-ruby-kubernetes-lb LoadBalancer 10.12.15.50 35.232.70.45 80:31354/TCP 17m
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deployment.apps/semaphore-demo-ruby-kubernetes 3 3 3 3 17m
NAME DESIRED CURRENT READY AGE
replicaset.apps/semaphore-demo-ruby-kubernetes-7d985f8b7c 3 3 3 2m3
IP-адрес службы отображается после модуля. У меня балансировщик нагрузки был назначен на внешний IP 35.232.70.45. Его нужно сменить на тот, который вам назначил ваш провайдер, тогда давайте опробуем новый сервер.
$ curl -w "\n" http://YOUR_EXTERNAL_IP/sing
Теперь конец не за горами.
Победа близка
Развертывание в Kubernetes не так сложно, если у вас есть правильное решение CI/CD. Теперь у вас есть полностью автоматизированный конвейер непрерывной доставки для Kubernetes.
Вот несколько предложений по разветвлению и экспериментам с semaphore-demo-ruby-kubernetes в Kubernetes по желанию:
- Создайте промежуточный кластер
- Создайте контейнер развертывания и запустите в нем тесты.
- Масштабируйте проект, добавляя больше микросервисов