Задача — развернуть CloudFromation стек с одним EC2 интансом и примонтировать Elastic Block Store (EBS ) с данными Jenkins .
Потом с помощью Ansible — установить там Docker и запустить Jenkins .
Подготовка
Сначала — вручную создадим EBS, который будет хранить данные Jenkins и далее будет подключаться к создаваемому EC2 интансу.
Создаём именованный профиль AWS CLI для RTFM:
aws configure --profile rtfm
AWS Access Key ID [None]: AKI***2PA
AWS Secret Access Key [None]: P6o***2VR
Default region name [None]: eu-west-1
Default output format [None]: json
EBS
Создаём EBS раздел.
Тип раздела — стандартный Magnetic (standard) , ибо дешёво и сердито — для Jenkins достаточно.
Стоимость EBS разделов — тут>>> , описание — тут>>> .
Создаём с помощью create-volume
:
aws ec2 --profile rtfm create-volume --availability-zone eu-west-1a --size 8 --volume-type standard --tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=jenkins-workspaces},{Key=Env,Value=production}]'
{
"AvailabilityZone": "eu-west-1a",
"CreateTime": "2017-09-30T11:47:30.863Z",
"Encrypted": false,
"Size": 8,
"SnapshotId": "",
"State": "creating",
"VolumeId": "vol-0085149b3a0a45d0c",
"Tags": [
{
"Key": "Name",
"Value": "jenkins-workspaces"
},
{
"Key": "Env",
"Value": "production"
}
],
"VolumeType": "standard"
}
Elastic IP (EIP ) будет добавляться во время создания стека, что бы не тратить деньги, когда стек будет удаляться (держать Jenkins запущенным постоянно я не планирую, ибо деньги — проще обновить IN A
запись домена при обновлении стека и EIP).
Домены со временем тоже планируется вынести в AWS Route 53 , пока управление — через панель регистратора.
Key pair
Создаём ключ для доступа с помощью create-key-pair
, сохраняем в файл ~/.ssh/rtfm_jenkins.pem
:
aws ec2 --profile rtfm create-key-pair --key-name rtfm_jenkins --query 'KeyMaterial' --output text > ~/.ssh/rtfm_jenkins.pem
Права:
chmod 400 ~/.ssh/rtfm_jenkins.pem
CloudFormation
Теперь приступаем к созданию шаблона.
Шаблон очень простой:
Security Group
EC2
подключить к нему созданный выше EBS
Примеры шаблонов для создания EC2 есть тут>>> и тут>>> .
AMI ID
Находим Debian AMI ID с помощью describe-images
:
aws ec2 --profile rtfm describe-images --filters "Name=name,Values=debian-stretch*" "Name=root-device-type,Values=ebs" "Name=image-type,Values=machine" "Name=manifest-location,Values=aws-marketplace*"
{
"Images": [
{
"Architecture": "x86_64",
"CreationDate": "2017-09-01T05:55:19.000Z",
"ImageId": "ami-cc5aa0b5",
"ImageLocation": "aws-marketplace/debian-stretch-hvm-x86_64-gp2-2017-08-31-64407-572488bb-fc09-4638-8628-e1e1d26436f4-ami-ac5e55d7.4",
...
Создание шаблона
Теперь — создаём шаблон.
Сначала — параметры:
InstanceType : тип инстанса, по умолчанию самый мелкий — t2.nano
, типы инстансов смотрим тут>>>
JenkinsAMIID : AMI ID Debian 9, который нашли выше
KeyName : ключ, созданный ранее
HomeAllowLocation : домашний IP, откуда разрешён доступ, позже можно будет добавить ещё один, для доступа с рабочей сети, передавать будем через параметры при создании стека
Ресурсы:
EC2Instance : параметры EC2
InstanceSecurityGroup : описание Security Group
Собственно — это всё, что требуется для минимального стека с EC2.
Полностью шаблон выглядит так:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Template EC2",
"Parameters" : {
"InstanceType": {
"Description": "Jenkins EC2 instance type",
"Type": "String",
"Default": "t2.nano",
"AllowedValues": [
"t2.nano",
"t2.micro",
"t2.small"
],
"ConstraintDescription": "Must be a valid EC2 instance type."
},
"AMIID": {
"Description": "Debian AMI ID",
"Type": "String",
"Default": "ami-cc5aa0b5"
},
"KeyName": {
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instance",
"Type": "AWS::EC2::KeyPair::KeyName",
"ConstraintDescription": "must be the name of an existing EC2 KeyPair.",
"Default": "rtfm_jenkins"
},
"HomeAllowLocation": {
"Description": "The IP address range that can be used to SSH to the EC2 instances",
"Type": "String",
"MinLength": "9",
"MaxLength": "18",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
}
},
"Resources" : {
"EC2Instance" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"Tags" : [
{
"Key" : "Name", "Value" : "rtfm_jenkins"
}
],
"InstanceType" : { "Ref" : "InstanceType" },
"SecurityGroups" : [
{
"Ref" : "InstanceSecurityGroup"
}
],
"KeyName" : { "Ref" : "KeyName" },
"ImageId" : { "Ref" : "AMIID" }
}
},
"InstanceSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"Tags" : [
{
"Key" : "Name", "Value" : "rtfm_jenkins"
}
],
"GroupDescription" : "Enable SSH access via port 22",
"SecurityGroupIngress" : [
{
"IpProtocol" : "tcp",
"FromPort" : "22",
"ToPort" : "22",
"CidrIp" : { "Ref" : "HomeAllowLocation"}
},
{
"IpProtocol" : "tcp",
"FromPort" : "80",
"ToPort" : "80",
"CidrIp" : { "Ref" : "HomeAllowLocation"}
},
{
"IpProtocol" : "tcp",
"FromPort" : "443",
"ToPort" : "443",
"CidrIp" : { "Ref" : "HomeAllowLocation"}
}
]
}
}
},
"Outputs" : {
"InstanceId" : {
"Description" : "InstanceId of the newly created EC2 instance",
"Value" : { "Ref" : "EC2Instance" }
},
"AZ" : {
"Description" : "Availability Zone of the newly created EC2 instance",
"Value" : { "Fn::GetAtt" : [ "EC2Instance", "AvailabilityZone" ] }
},
"PublicDNS" : {
"Description" : "Public DNSName of the newly created EC2 instance",
"Value" : { "Fn::GetAtt" : [ "EC2Instance", "PublicDnsName" ] }
},
"PublicIP" : {
"Description" : "Public IP address of the newly created EC2 instance",
"Value" : { "Fn::GetAtt" : [ "EC2Instance", "PublicIp" ] }
}
}
}
validate-template
Проверяем шаблон с помощью validate-template
:
cd PycharmProjects/rtfm_jenkins_stack/
ls -l
total 4
-rw-r--r-- 1 setevoy setevoy 2988 Sep 30 16:09 jenkins_ec2.template
aws cloudformation --profile rtfm validate-template --template-body file://jenkins_ec2.template
{
"Parameters": [
{
"ParameterKey": "AMIID",
"DefaultValue": "ami-cc5aa0b5",
"NoEcho": false,
"Description": "Debian AMI ID"
},
{
"ParameterKey": "KeyName",
"DefaultValue": "rtfm_jenkins",
"NoEcho": false,
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instance"
},
{
"ParameterKey": "HomeAllowLocation",
"NoEcho": false,
"Description": "The IP address range that can be used to SSH to the EC2 instances"
},
{
"ParameterKey": "InstanceType",
"DefaultValue": "t2.nano",
"NoEcho": false,
"Description": "Jenkins EC2 instance type"
}
],
"Description": "AWS CloudFormation Template EC2"
}
create-stack
И создаём стек с помощью create-stack
, передавая параметром IP:
aws cloudformation --profile rtfm create-stack --stack-name rtfm-jenkins --disable-rollback --template-body file://jenkins_ec2.template --parameters ParameterKey=HomeAllowLocation,ParameterValue=188.***.***.114/32
{
"StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/rtfm-jenkins/59633cf0-a5e1-11e7-b8cc-503ac9e74cc5"
}
Проверяем статус:
aws cloudformation --profile rtfm describe-stacks --stack-name rtfm-jenkins --query 'Stacks[*].StackStatus'
[
"CREATE_COMPLETE"
]
В консоли AWS :
update-stack
Надо было имя для EC и Security Group сразу задать, через теги…
Обновим шаблон, ресурс EC2Instance :
...
"Resources" : {
"EC2Instance" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"Tags" : [
{
"Key" : "Name", "Value" : "rtfm_jenkins"
}
],
...
И InstanceSecurityGroup :
...
"InstanceSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"Tags" : [
{
"Key" : "Name", "Value" : "rtfm_jenkins"
}
],
...
Выполняем update-stack
:
aws cloudformation --profile rtfm update-stack --stack-name rtfm-jenkins --template-body file://jenkins_ec2.template --parameters ParameterKey=HomeAllowLocation,ParameterValue=188.***.***
.114/32
{
"StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/rtfm-jenkins/59633cf0-a5e1-11e7-b8cc-503ac9e74cc5"
}
Проверяем статус:
aws cloudformation --profile rtfm describe-stacks --stack-name rtfm-jenkins --query 'Stacks[*].StackStatus'
[
"UPDATE_COMPLETE"
]
Проверяем инстанс:
aws ec2 --profile rtfm describe-instances --filters Name=tag-value,Values=rtfm_jenkins
{
"Reservations": [
{
"Groups": [],
"Instances": [
{
"AmiLaunchIndex": 0,
"ImageId": "ami-cc5aa0b5",
"InstanceId": "i-06cb3e0a8ce7a8e59",
"InstanceType": "t2.nano",
"KeyName": "rtfm_jenkins",
...
Замечательно.
Attach EBS
Следующим шагом — добавим подключение созданного в самом начале EBS раздела.
Обновляем шаблон, в параметры добавляем ID EBS раздела:
...
"JenkinsWorkspacesEBSID": {
"Description": "Existing EBS volume with Jenkins worspaces",
"Type": "String",
"Default": "vol-0085149b3a0a45d0c"
}
...
В ресурсах — обновляем EC2, добавляем в Property — Volume
:
...
"Resources" : {
"EC2Instance" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"Tags" : [
{
"Key" : "Name", "Value" : "rtfm_jenkins"
}
],
"InstanceType" : { "Ref" : "InstanceType" },
"SecurityGroups" : [
{
"Ref" : "InstanceSecurityGroup"
}
],
"KeyName" : { "Ref" : "KeyName" },
"ImageId" : { "Ref" : "AMIID" },
"Volumes" : [
{
"VolumeId" : { "Ref" : "JenkinsWorkspacesEBSID" },
"Device" : "/dev/xvdb"
}
]
}
},
...
Проверяем шаблон:
aws cloudformation --profile rtfm validate-template --template-body file://jenkins_ec2.template
Обновляем стек:
aws cloudformation --profile rtfm update-stack --stack-name rtfm-jenkins --template-body file://jenkins_ec2.template --parameters ParameterKey=HomeAllowLocation,ParameterValue=188.***.***.114/32
И апдейт падает с ошибкой «The volume ‘vol-0085149b3a0a45d0c’ is not in the same availability zone as instance ‘i-06cb3e0a8ce7a8e59’ «.
ОК, забыл.
В параметры — добавляем Availability Zone :
...
"JenkinsAvailabilityZone": {
"Description": "AZ for Jenkins EC2, must be same as Workspaces EBS",
"Type": "String",
"Default": "eu-west-1a"
}
...
В Properties
ресурса EC2 — добавляем указание «AvailabilityZone «:
...
"Resources" : {
"EC2Instance" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"AvailabilityZone" : { "Ref" : "JenkinsAvailabilityZone" },
"Tags" : [
{
"Key" : "Name", "Value" : "rtfm_jenkins"
}
],
...
Обновляем:
aws cloudformation --profile rtfm update-stack --stack-name rtfm-jenkins --template-body file://jenkins_ec2.template --parameters ParameterKey=HomeAllowLocation,ParameterValue=188.***.***.114/32
В Консоли:
Проверяем разделы интанса:
aws ec2 --profile rtfm describe-instances --filters Name=tag-value,Values=rtfm_jenkins --query 'Reservations[*].Instances[*].BlockDeviceMappings' --output text
xvda
EBS 2017-09-30T13:52:50.000Z True attached vol-0edd79eb7741a3bd7
/dev/xvdb
EBS 2017-09-30T13:56:12.000Z False attached vol-0085149b3a0a45d0c
Логинимся на сервер, проверяем разделы:
ssh admin@52.210.238.7 -i ~/.ssh/rtfm_jenkins.pem
admin@ip-172-31-20-149:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda 202:0 0 8G 0 disk
└─xvda1 202:1 0 8G 0 part /
xvdb 202:16 0 8G 0 disk
Jenkins
Дальше установка Jenkins будет выполнятся с помощью Ansbile — а сейчас выполним это руками, что бы иметь готовый EBS с worskpaces
.
xvdb
Форматируем раздел xvdb
.
Будьте осторожны с sfdisk
— синтаксис немного… Неудобный.
Выглядит команда так:
sfdisk /dev/xvdb << EOF
;
EOF
Выполняем:
root@ip-172-31-20-149:/home/admin# sfdisk /dev/xvdb << EOF
> ;
> EOF
Checking that no-one is using this disk right now ... OK
Disk /dev/xvdb: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x7feabcd9
Old situation:
>>> Created a new DOS disklabel with disk identifier 0x1f89d3bd.
/dev/xvdb1: Created a new partition 1 of type 'Linux' and of size 8 GiB.
/dev/xvdb2: Done.
New situation:
Device Boot Start End Sectors Size Id Type
/dev/xvdb1 2048 16777215 16775168 8G 83 Linux
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
Проверяем:
root@ip-172-31-20-149:/home/admin# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda 202:0 0 8G 0 disk
└─xvda1 202:1 0 8G 0 part /
xvdb 202:16 0 8G 0 disk
└─xvdb1 202:17 0 8G 0 part
Создаём файловую систему:
root@ip-172-31-20-149:/home/admin# mkfs.ext4 /dev/xvdb1
mke2fs 1.43.4 (31-Jan-2017)
Creating filesystem with 2096896 4k blocks and 524288 inodes
Filesystem UUID: 3818cfb8-c1ac-46d1-87ac-55700dc0f473
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632
Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done
Монтируем /dev/xvdb1
в каталог /jenkins
:
root@ip-172-31-20-149:/home/admin# mkdir /jenkins
root@ip-172-31-20-149:/home/admin# mount /dev/xvdb1 /jenkins/
Docker
Устанавливаем Docker :
root@ip-172-31-20-149:/home/admin# apt update && apt -y install curl
root@ip-172-31-20-149:/home/admin# curl https://get.docker.com/ | bash
Проверяем:
root@ip-172-31-20-149:/home/admin# docker --version
Docker version 17.09.0-ce, build afdb6d4
Запуск Jenkins
Запускаем Docker контейнер с Jenkins , монтируем каталог /jenkins
с хост-машины в каталог /jenkins
контейнера, и указываем переменную JENKINS_HOME=/jenkins
:
docker run -ti -u 0 -p 80:8080 -v /jenkins/:/jenkins -v /var/run/docker.sock:/var/run/docker.sock -e JENKINS_HOME='/jenkins' jenkins
...
Sep 30, 2017 2:38:18 PM hudson.model.AsyncPeriodicWork$1 run
INFO: Finished Download metadata. 9,353 ms
--> setting agent port for jnlp
--> setting agent port for jnlp... done
Собственно — всё.
Активируем Jenkins в UI:
Проверяем содержимое /dev/xvdb1
:
admin@ip-172-31-20-149:~$ ls -l /jenkins/
total 100
-rw-r--r-- 1 root root 1592 Sep 30 14:38 config.xml
-rw-r--r-- 1 root root 159 Sep 30 14:38 hudson.model.UpdateCenter.xml
-rw-r--r-- 1 root root 370 Sep 30 14:40 hudson.plugins.git.GitTool.xml
-rw------- 1 root root 1712 Sep 30 14:38 identity.key.enc
...
drwxr-xr-x 3 root root 4096 Sep 30 14:38 users
drwxr-xr-x 10 root root 4096 Sep 30 14:38 war
drwxr-xr-x 2 root root 4096 Sep 30 14:40 workflow-libs
Пересоздание стека
Для полноты картины — теперь можно всё удалить — и создать заново:
aws cloudformation --profile rtfm delete-stack --stack-name rtfm-jenkins
Проверяем состояние EBS:
aws ec2 --profile rtfm describe-volumes --volume-ids vol-0085149b3a0a45d0c --query 'Volumes[*].State' --output text
available
Пересоздаём стек:
aws cloudformation --profile rtfm create-stack --stack-name rtfm-jenkins --disable-rollback --template-body file://jenkins_ec2.template --parameters ParameterKey=HomeAllowLocation,ParameterValue=188.***.***.114/32
Подключаемся к новому инстансу, проверяем разделы:
ssh admin@52.214.1.245 -i ~/.ssh/rtfm_jenkins.pem
...
admin@ip-172-31-31-164:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda 202:0 0 8G 0 disk
└─xvda1 202:1 0 8G 0 part /
xvdb 202:16 0 8G 0 disk
└─xvdb1 202:17 0 8G 0 part
Монтируем, проверяем данные:
root@ip-172-31-31-164:/home/admin# mkdir /jenkins
root@ip-172-31-31-164:/home/admin# mount /dev/xvdb1 /jenkins/
root@ip-172-31-31-164:/home/admin# ls -l /jenkins/
total 108
-rw-r--r-- 1 root root 1592 Sep 30 14:38 config.xml
-rw-r--r-- 1 root root 159 Sep 30 14:38 hudson.model.UpdateCenter.xml
-rw-r--r-- 1 root root 370 Sep 30 14:40 hudson.plugins.git.GitTool.xml
...
drwxr-xr-x 10 root root 4096 Sep 30 14:38 war
drwxr-xr-x 2 root root 4096 Sep 30 14:40 workflow-libs
Устанавливаем Docker :
root@ip-172-31-31-164:/home/admin# apt update && apt -y install curl && curl https://get.docker.com/ | bash
Повторяем запуск Jenkins :
docker run -ti -u 0 -p 80:8080 -v /jenkins/:/jenkins -v /var/run/docker.sock:/var/run/docker.sock -e JENKINS_HOME='/jenkins' jenkins
Всё на месте:
Готово.
Шаблон доступен в Github тут>>> .
Ссылки по теме
Собственно — все ссылки на этот же блог, ибо всё уже делалось ранее:
AWS: CloudFormation – создание шаблона для VPC, EC2, NAT и Internet Gateway
AWS: миграция RTFM, часть #1: ручное создание инфраструктуры – VPC, подсети, IGW, NAT GW, маршруты и EC2
AWS: миграция RTFM, часть #3: CloudFormation – инфрастуктура
AWS: смонтировать EBS к EC2
Docker: AWS [China] – Jenkins в Docker
Azure: Azure Resource Manager provisioning и Jenkins в Docker