Диплом: Автоматизация доставки программного обеспечения при помощи DevOps практик и инструментов в облаке AWS в компании ООО "Команда Лабс"

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам
71
Рисунок 30. Проекты внутри сервера непрерывной интеграции и
деплоймента
Как видим, на Рисунке 22 страницы проектов в TeamCity Server, у нас есть
несколько окружений для сборки, тестирования и выкатывания ПО в
производственную среду. Выкатывание должно проходить в 2 этапа: на первом
этапе ПО собирается при помощи Apache Maven v 3.6.3 [9] — фреймворка для
автоматизации сборки проектов на основе описания их структуры в файлах на
языке POM, являющемся подмножеством XML. На втором этапе собранные
артефакты отправляются в Docker [8] контейнеры, листинг которых мы можем
наблюдать ниже:
FROM alpine:3.11.3
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
LC_ALL='en_US.UTF-8'
ENV JAVA_VERSION jdk-12.0.1+12
ENV JAVA_HOME=/opt/java/openjdk \
PATH="/opt/java/openjdk/bin:$PATH"
ADD target/service-distribution/lib /usr/share/jvmservice/lib
ADD target/service-distribution/analytics-api-0.31.0.jar
/usr/share/jvmservice/service.jar
CMD java $JAVA_OPTS -jar /usr/share/jvmservice/service.jar
В примере выше мы берём Docker 19.03.2, образ OS Alpine 3.11.3 (я не
привожу полный листинг), устанавливаем Java 12.0.1 и затем добавляем внутрь
72
образа артефакт, который был создан на предыдущем шаге, в данном случае это
«ADD target/service-distribution/analytics-api-0.31.0.jar».
Затем полученный образ нам необходимо развернуть на Elastic Container
Service [7] при помощи bash скрипта (ниже я оставляю комментарии красным
курсивом):
Скрипт разворачивания сервисов внутри Elastic Container Service
# Используем ключ доступа и секретный ключ для пользователя, который
# имеет право создавать ресурсы в сервисе ECS [7]:
echo "Account keys is " $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY
export AWS_DEFAULT_REGION=%region%
# Определяем расположение проекта на сборочном агенте TeamCity
PROJECT_LOCATION=$(eval pwd)
echo project location is $PROJECT_LOCATION
# Определяем политику запуска микросервиса в ECS [7]:
PATH_TO_TASK_DEFENITION=%path_to_json%
# И версию приложения, которая указана в Maven [9] XML
vers=%maven.project.version%
VERSI="${vers//[!0-9\.]/}"
echo $vers
echo $VERSI
JSON=.json
FILE=file://
# Определяем, в какое окружение мы хотим раскатать наш микросервис
CLUSTER=%cluster_env%
ECR_ID=%ecr_id%
ENV=%infra%
PROJ=hapi-
SLASH=/
DASH=_
echo $PROJECT_LOCATION
cd $PROJECT_LOCATION
# Задаём теги для образа, который мы хотим сохранить в
# Elastic Container Registry
BUILD_NUMBER=%build.number%
GIT_HASH=%build.vcs.number.Hapi_ProdEu_Bitbucket%
echo $GIT_HASH
GIT_HASH_SHORT=${GIT_HASH:0:7}
echo $GIT_HASH_SHORT
version=`echo $ENV$DASH$VERSI`
echo 'Version will be used:' $version
GIT_TAG=$GIT_HASH_SHORT
# Удаляем старые отчёты, созданный утилитой Trivy [19], при проверке на уязвимости
# созданного во время предыдущей сборки
find . -name "report*.txt" -exec rm -rf {} \;
cd %system.teamcity.build.workingDir%
# Устанавливаем Trivy [19]
#export TRIVY_VERSION=$(curl –silent) "https://api.github.com/repos/knqyf263/trivy/releases/latest" |
grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
73
wget
https://github.com/knqyf263/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-
64bit.tar.gz
tar zxvf trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz
# Находим при помощи цикла while все файлы, которые называются Dockerfile [8]
find . -name Dockerfile | while read -r d; do
part1=`dirname "$d"`
part2=`basename "$d"`
echo 'Folder is ' $part1
# Исключаем из сборки ненужные приложения
if [[ ! ($part1 == *"test-message-receiver"* || $part1 == *"roomkey-producer"* || $part1 == *"logus-
producer"* || $part1 == *"sample-app"* || $part1 == *"rlh-il-consumer"* || $part1 == *"generic-router"* || $part1 ==
*"generic-transformer"*) ]]; then
# Ищем название раскатываемого приложения по названию папки в Git репозитории
FULL_HAPI_APP_NAME=`echo "$part1" | sed 's#./##'`
FULL_HAPI_REPO_NAME=`echo $PROJ$ENV$SLASH$FULL_HAPI_APP_NAME`
# Вызываем конфигуратор для AWS аккаунта
aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
aws configure set default.region $AWS_DEFAULT_REGION
# И создаём репозиторий для хранения созданного образа в
# Elastic Container Repository [7]
echo 'Start create ecr for FULL_HAPI_APP_NAME: ' $FULL_HAPI_APP_NAME '- result name is
FULL_HAPI_REPO_NAME: '$FULL_HAPI_REPO_NAME
# Если репозиторий с этим именем не существует
aws ecr describe-repositories --repository-names $FULL_HAPI_REPO_NAME 2>&1 > /dev/null
status=$?
if [[ ! "${status}" -eq 0 ]]; then
echo 'Start create ecr for' $FULL_HAPI_APP_NAME '- result name is
'$FULL_HAPI_REPO_NAME
aws ecr create-repository --repository-name $FULL_HAPI_REPO_NAME
aws ecr put-lifecycle-policy --repository-name "hapi-$ENV/$FULL_HAPI_APP_NAME" --
lifecycle-policy-text "file://%system.teamcity.build.checkoutDir%/env/aws/dev/lifecycle_policy/default_policy.json"
fi
# Стартуем сборку проекта и добавление тегов для Docker [8] образов
$(aws ecr get-login --no-include-email --region %region%)
cd $PROJECT_LOCATION/$FULL_HAPI_APP_NAME
echo 'Start build and push project' $FULL_HAPI_APP_NAME 'with version tag' $version
docker build -t hapi-$ENV/$FULL_HAPI_APP_NAME .
docker tag hapi-$ENV/$FULL_HAPI_APP_NAME:latest
$ECR_ID.dkr.ecr.%region%.amazonaws.com/hapi-$ENV/$FULL_HAPI_APP_NAME:latest
docker tag hapi-$ENV/$FULL_HAPI_APP_NAME:latest
$ECR_ID.dkr.ecr.%region%.amazonaws.com/hapi-$ENV/$FULL_HAPI_APP_NAME:$version
docker tag hapi-$ENV/$FULL_HAPI_APP_NAME:latest
$ECR_ID.dkr.ecr.%region%.amazonaws.com/hapi-$ENV/$FULL_HAPI_APP_NAME:$GIT_TAG
# И выгружаем созданные образы в сервис AWS Elastic Container Repository [7]
docker push $ECR_ID.dkr.ecr.%region%.amazonaws.com/hapi-
$ENV/$FULL_HAPI_APP_NAME:latest
docker push $ECR_ID.dkr.ecr.%region%.amazonaws.com/hapi-
$ENV/$FULL_HAPI_APP_NAME:$version
docker push $ECR_ID.dkr.ecr.%region%.amazonaws.com/hapi-
$ENV/$FULL_HAPI_APP_NAME:$GIT_TAG
# Запускаем проверку образов на уязвимости при помощи утилиты Trivy [19]
echo 'Scan trivy start'
trivy --exit-code 1 --severity HIGH --quiet --auto-refresh
$ECR_ID.dkr.ecr.%region%.amazonaws.com/hapi-$ENV/$FULL_HAPI_APP_NAME:$GIT_TAG |& tee -a
$PROJECT_LOCATION/report.txt
trivy --exit-code 1 --severity CRITICAL --quiet --auto-refresh
$ECR_ID.dkr.ecr.%region%.amazonaws.com/hapi-$ENV/$FULL_HAPI_APP_NAME:$GIT_TAG |& tee -a
$PROJECT_LOCATION/report_critical.txt
74
# Need to substring the latest part from i.e. producers/hapi-producer
HAPI_APP_NAME=`string="$FULL_HAPI_APP_NAME" && printf "%s\n" "${string##*/}"`
echo 'Result HAPI_APP_NAME = '$HAPI_APP_NAME
TASK_FAMILY=`echo $ENV$DASH$HAPI_APP_NAME`
# Далее нам необходимо создать соответствующие сервисы внутри
# AWS Elastic Container Service [7], если ещё не были созданы с таким именем:
list_tasks=`aws ecs list-task-definition-families --family-prefix $TASK_FAMILY`
echo 'Found list tasks = '"$list_tasks"
if [[ ! "$list_tasks" == *"$TASK_FAMILY"* ]]; then
IMGAGE_PLACEHOLDER="<IMAGE_FULL_PATH_WITH_VERSION_PLACEHOLDER>"
ls -la %system.teamcity.build.checkoutDir%/$PATH_TO_TASK_DEFENITION
RESUL
TASK_DEFINITION_FILE=$(cat
%system.teamcity.build.checkoutDir%/$PATH_TO_TASK_DEFENITION/$HAPI_APP_NAME$JSON)
echo "Definition file json is TASK_DEFINITION_FILE: " $TASK_DEFINITION_FILE
IMAGE_FULL_PATH_WITH_VERSION=$ECR_ID.dkr.ecr.%region%.amazonaws.com/hapi-
$ENV/$FULL_HAPI_APP_NAME:$GIT_TAG
echo 'Image full path will be used IMAGE_FULL_PATH_WITH_VERSION: '
$IMAGE_FULL_PATH_WITH_VERSION
echo 'Start build task definition family' $TASK_FAMILY 'with container definition file'
$TASK_DEFINITION_FILE
TASK_DEFINITION_RESULT="${TASK_DEFINITION_FILE//$IMGAGE_PLACEHOLDER/$IMAGE_
FULL_PATH_WITH_VERSION}"
echo 'Resulted json definition after insert image url: ' $TASK_DEFINITION_RESULT
RESULT_TASK_DEFINITION_FILE=$TASK_FAMILY$JSON
touch RESULT_TASK_DEFINITION_FILE
echo "$TASK_DEFINITION_RESULT" > "$RESULT_TASK_DEFINITION_FILE"
export TASK_VERSION=$(aws ecs register-task-definition --family ${TASK_FAMILY} --cli-input-
json file://$RESULT_TASK_DEFINITION_FILE --debug | jq --raw-output '.taskDefinition.revision')
echo "Registered ECS Task Definition: " $TASK_VERSION
if [ -n "$TASK_VERSION" ]; then
echo "Update ECS Cluster: " $CLUSTER
echo "Service: " $HAPI_APP_NAME
echo "Task Definition: " $TASK_FAMILY:$TASK_VERSION
DEPLOYED_SERVICE=$(aws ecs update-service --cluster $CLUSTER --service
$HAPI_APP_NAME --task-definition $TASK_FAMILY:$TASK_VERSION | jq --raw-output '.service.serviceName')
echo "Deployment of $DEPLOYED_SERVICE complete"
else
echo "exit: No task definition"
exit;
fi
else
echo "Task definition with family $TASK_FAMILY exists. Update existing service
$HAPI_APP_NAME in cluster $CLUSTER"
# И наконец задеплоить и запустить соответствующие сервисы внутри кластера
# Elastic Container Service [7]
ecs deploy $CLUSTER $HAPI_APP_NAME -t $GIT_TAG --timeout -1 --no-deregister
fi
fi
done
Таким образом, весь процесс деплоймента кода автоматизирован, на
выходе мы получаем кластер ECS, к которому можно обращаться через прокси
сервер Nginx 1.17.7.
Проксирование трафика к сервисам при помощи Nginx и AWS NLB
После того, как мы смогли настроить и запустить задеплоенные сервисы,
нам необходимо обеспечить к ним доступ из внешнего мира. Для того, чтобы
75
проксировать на них запросы от клиентов мы воспользуемся web-сервером Nginx
[13], аналогов которого среди свободно распространяемого софта фактически
нет. Соответственно, мы сможем избежать дополнительных затрат при очень
быстро работающем прокси-сервере. Для того, чтобы использовать свои модули
для обеспечения передачи данных в API наших сервисов, мы решили
использовать ручную сборку сервера Nginx [13] версии 1.17.7 на системе Ubuntu
18.04 из исходного кода. Нам понадобится модуль headers-more-nginx-module
[33]. Этот модуль позволяет добавлять, устанавливать или очищать любой
выходной или входной заголовок, который будет указан. В нашем случае это
будет more_set_headers "Server: HAPI", что позволит добавлять дополнительные
заголовки для взаимодействия с нашим сервером. Я написал bash скрипт,
который позволяет собрать сервер из исходного кода. Полный листинг сборки я
приводить не буду, лишь приведу часть, где необходимо сконфигурировать
необходимые кастомные модули для работы, мы их задаём через переменные
окружения:
--with-http_ssl_module \ # PCI DSS [29]
--with-stream_ssl_module \ # нам необходимо для PCI DSS [29]
--add-module=$HEADERS_MORE_FOLDER_PATH \
--with-cc-opt='-g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -
Werror=format-security -Wp,-D_FORTIFY_SOURCE=2' \
--with-ld-opt='-Wl,-z,relro -Wl,--as-needed' >> ${INSTALL_LOG_FILE_PATH}
2>&1
# Build nginx with the specified configuration
sudo make >> ${INSTALL_LOG_FILE_PATH} 2>&1
76
Рисунок 31. Балансировка трафика при помощи AWS Network Load
Balancer
Далее воспользуемся сервисом AWS Auto Scaling Group. Для того, чтобы
сервис понимал, какие серверы он должен масштабировать, мы должны ему
предоставить заранее созданные образы. Так как выбор ПО для создания
подобных образов на рынке не особенно много, и при наличии в нашей команде
экспертизы в использовании, мы решили собирать образы при помощи Packer
1.5.4 [27] от компании HashiCorp. При запуске Packer 1.5.4 [27] мы должны
предоставить ему json файл с декларативным описанием того, что мы хотим
видеть внутри полученного образа. Внутри собираемого образа мы собираем
Nginx [13] из исходного кода, как описывалось ранее. А также запускаем
скрипты конфигурации сервера и проверка после перезагрузки сервера его
работоспособности. За основу мы берём последнюю сборку сервера Ubuntu
18.04, и в роли билдера мы должны указать amazon-ebs, это значит, что мы будем
разворачивать его внутри AWS.
77
После сборки образа Nginx [13] сервера мы можем добавить имя
полученного AMI [34] (Amazon Machine Images) в сервис, который будет
управлять количеством запущенных данных инстансов Auto Scaling Launch
Configurations. Каждый раз, когда мы будем обновлять сервер с Nginx, мы
должны новый AMI добавлять сюда в рамках PCI DSS [29].
Рисунок 32. Настройа Launch Configuration
После необходимо создать Auto Scaling группу, которая будет следить за
количество запущенных инстансов, в нашем случае нам необходимо по одному
серверу на каждую Availability Zone (дата-центр AWS внутри одного региона, в
нашем случае это eu-central-1 — Франкфурт). В роли балансировщика выбираем
созданный ранее NLB:
Рисунок 33. Создание Auto Scalling группы
78
Итак, у нас должно быть 2 созданных Nginx 1.17.7 [13]с ервера, при этом,
если любой из них перестаёт быть доступным, он удаляется из таргетной группы,
терминируется и создаётся новый инстанс, созданный на основе Packer 1.5.4
образа. В случае увеличения трафика мы можем задать большее количество
желаемых инстансов в 2 или 3 дата-центрах.
Разворачивание инфраструктуры в AWS при помощи Terraform
Одна из самых важных задач DevOps состоит в том, чтобы максимально
автоматизировать разворачивание инфраструктуры. А также в рамках PCI DSS
[29] необходимо регулярно проводить учения по Disaster Recovery, где все
участники команды должны знать, как в любой момент развернуть
инфраструктуру за кратчайшие сроки на случай падения всей системы или
разрушительной атаки извне с деградацией сервисов. Для подобной практики мы
будем использовать бесплатное решение от HashiCorp под названием Terraform
0.12.23 [22]. Оно поможет нам в декларативной форме описать желаемую
инфраструктуру, а также совместно всей командой управлять состоянием
системы с помощью сервиса Amazon S3.
Кодовая база инфраструктурного кода для удобства поддержки и
переиспользования кода должна быть разделена на модули. Целью
разворачивания инфраструктуры должна быть следующая схема:
79
Рисунок 34. Необходимая инфраструктура
Логирование, алертинг и мониторинг
Для полного понимания того, что происходит в нашей системе, в каждом
из окружений, нам необходимо собирать события со всех инстансов EC2 c
Ubuntu 18.04 [15] и из сервисов ECS [7], складывать их в централизованное
хранилище и затем нам нужен инструмент для аналитики записанных событий.
Для агрегации логов вполне подходит сервис, который из коробки предоставляет
AWS, CloudWatch [35]. Он предоставляет инструменты для интеграции, для
помещения событий в топики, настройки оповещений, аналитики. Но мы хотели
более удобного инструмента для работы с логами, такой, как стек ELK 7.1 [20]
(Elasticsearch, Logstash, Kibana), который де-факто считается промышленным
стандартом.
80
Рисунок 35. Информационная модель
2.2.2 Характеристика нормативно-справочной, входной и оперативной
информации
Для гибких подходов к разворачиванию инфраструктуры и доставки кода
мы решили применять подход Инфраструктура как код (Infrastructure as Code,
IaC) [3]. Исходным документом является код от программистов и
автоматизаторов тестирования, в котором они в декларативном виде описывают
желаемые характеристики системы, который затем попадает в Git репозиторий.
Оттуда код забирается по расписанию при помощи сервера интеграции TeamCity
с помощью скриптов выполняется и разворачивается в желаемом окружении.
Затем со всех серверов собирается информация в виде логов и состояниях
системы и складывается в ElasticSearch 7.1 и InfluxDB 2.0 соответственно. Таким

Смотрите также:

"Автоматизация обработки заявок ООО "Проектно-Строительная Компания"
"Автоматизация процесса аттестации персонала для ООО "Нэт Бай Нэт Холдинг"
"Анализ интернет-активности конкурентов ( на примере конкурентов "Газпром нефть")
"Бухгалтерский учёт и аудит расчётов с подотчётними лицами в организации на примере ООО "ЛОЦ 10""
«Психологическое сопровождение персонала в организации на примере ООО «Крокус»
Cовершенствование деловой оценки персонала в организации (на примере ООО "Даймонд кейтеринг развитие")
IPO - инструмент финансирования деятельности организации. На примере ПАО «Нефтяная компания «Лукойл»
PR как средство продвижения организации (на примере ПАО "Тамбовский завод "Комсомолец им. Н.С. Артемова")
PR-коммуникации в сфере общественного питания (на примере кафе-кондитерской «Cream Cheese»)
SMM как средство повышения эффективности работы учреждений социокультурной сферы (на примере Малого театра)