Дипломная работа: Автоматизация доставки программного обеспечения при помощи 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
Источник: https://baza.diplomsite.ru/previewfile/1758