Azure: Azure Resource Manager provisioning и Jenkins в Docker

Автор: | 02/28/2017
 

Задача: создать Resource Group в Azure, которая будет включать в себя виртуальную машину с Jenkins-ом.

Jenkins будет запускаться в Docker-контейнере, и подгружать $JENKINS_HOME с внешнего диска, подключенного к виртуальной машине.

Получившийся шаблон можно посмотреть тут>>>.

Диск уже создан, имеет файловую систему и данные с текущего интанса Jenkins:

az disk list
...
{
"accountType": "Premium_LRS",
"creationData": {
"createOption": "Empty",
"imageReference": null,
"sourceResourceId": null,
"sourceUri": null,
"storageAccountId": null
},
"diskSizeGb": 100,
"encryptionSettings": null,
"id": "/subscriptions/0a4f2b9c-***-***-***-40b17ef8c3ab/resourceGroups/EUROPE-JM/providers/Microsoft.Compute/disks/jenkinsworkspaces",
"location": "westeurope",
"name": "jenkinsworkspaces",
"osType": null,
"ownerId": "/subscriptions/0a4f2b9c-***-***-***-40b17ef8c3ab/resourceGroups/jm-jenkins/providers/Microsoft.Compute/virtualMachines/jm-jenkins-vm",
"provisioningState": "Succeeded",
"resourceGroup": "EUROPE-JM",
"tags": null,
"timeCreated": "2017-02-27T11:38:22.992122+00:00",
"type": "Microsoft.Compute/disks"
},
...

Azure Resource Manager шаблон 

Параметры

За основу возьмём шаблон 101-vm-simple-linux.

Первым делом — обновляем параметры. Для нашей сети требуется добавить набор правил фаервола, для доступа из определённых сетей.

Выносим их в параметры.

В файл самого шаблона — добавляем:

...
    "CiklumNET": {
      "type": "string",
      "defaultValue": "194.***-***.45/32"
    },
    "BerlinNET": {
      "type": "string",
      "defaultValue": "37.***-***.130/32"
    },
    "Github": {
      "type": "string",
      "defaultValue": "192.30.252.0/22"
    },
    "SetevoyHome": {
      "type": "string",
      "defaultValue": "176.***-***.79"
    },
...

В файл с параметрами:

...
    "CiklumNET": {
      "value": "194.***-***.45/32"
    },
    "BerlinNET": {
      "value": "37.***-***.130/32"
    },
    "Github": {
      "value": "192.30.252.0/22"
    },
    "SetevoyHome": {
      "value": "176.***-***.79"
    }
  }
...

Заодно — обновляем значения остальных параметров, например:

...
    "dnsLabelPrefix": {
      "value": "jm-jenkins"
    },
...

Переменные

Обновляем значения для переменных, например:

...
    "OSDiskName": "jm-jenkins-os-disk",
    "nicName": "jm-jenkins-nic",
...

Кроме того — т.к. имеющийся диск является Managed диском и тип диска — Standard_LRS, то необходимо изменить тип виртуальной машины, т.к. Managed Disks поддерживаются только машинами уровня DS2 и выше. Больше деталей о типах ВМ — тут>>>, про Managed disksтут>>> и несколько ссылок — в конце поста.

Меняем значение переменной:

...
    "vmSize": "Standard_DS2_v2",
...

Блок variables получается таким:

...
  "variables": {
    "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'jm')]",
    "dataDisk1VhdName": "jm-jenkins-data-disk",
    "imagePublisher": "Canonical",
    "imageOffer": "UbuntuServer",
    "OSDiskName": "jm-jenkins-os-disk",
    "nicName": "jm-jenkins-nic",
    "NSGName": "jm-jenkins-nsg",
    "addressPrefix": "10.0.0.0/16",
    "subnetName": "jm-jenkins-subnet",
    "subnetPrefix": "10.0.0.0/24",
    "storageAccountType": "Standard_LRS",
    "publicIPAddressName": "jm-jenkins-pub-ip",
    "publicIPAddressType": "Dynamic",
    "vmStorageAccountContainerName": "vhds",
    "vmName": "jm-jenkins-vm",
    "vmSize": "Standard_DS2_v2",
    "virtualNetworkName": "jm-jenkins-vnet",
    "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]",
    "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]"
  },
...

Network Security Group

Переходим к ресурсам.

Добавляем создание Network Security Group, в properties которой указываем правила доступа, используя сети из параметров, которые были добавлены в начале.

Полностью ресурс будет выглядеть так:

...
   {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Network/networkSecurityGroups",
      "name": "[variables('NSGName')]",
      "location": "[resourceGroup().location]",
      "tags": {
        "displayName": "NSG - Remote Access"
      },
      "properties": {
        "securityRules": [
          {
            "name": "AllowSSHAll",
            "properties": {
              "description": "Allow SSH",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "22",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 100,
              "direction": "Inbound"
            }
          },
          {
            "name": "CiklumAllowAll",
            "properties": {
              "description": "Ciklum Allow All",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "*",
              "sourceAddressPrefix": "[parameters('CiklumNET')]",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 110,
              "direction": "Inbound"
            }
          },
                    {
            "name": "BerlinAllowAll",
            "properties": {
              "description": "Berlin Allow All",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "*",
              "sourceAddressPrefix": "[parameters('BerlinNET')]",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 120,
              "direction": "Inbound"
            }
          },
                    {
            "name": "Github",
            "properties": {
              "description": "Github Allow HTTP",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "[parameters('Github')]",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 130,
              "direction": "Inbound"
            }
          },
                    {
            "name": "ArsenyHomeAllowAll",
            "properties": {
              "description": "Arseny home Allow All",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "*",
              "sourceAddressPrefix": "[parameters('SetevoyHome')]",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 140,
              "direction": "Inbound"
            }
          }
        ]
      }
    },
...

Подключение Managed диска к создаваемой VM

Обновляем ресурс Microsoft.Compute/virtualMachines, и меням значение для dataDisks.

Исходные данные:

...
          "dataDisks": [
            {
              "diskSizeGB": "1023",
              "lun": 0,
              "createOption": "Empty"
            }
          ]
...

Указываем подключение своего диска:

...
          "dataDisks": [
              {
                  "lun": 0,
                  "createOption": "Attach",
                  "caching": "None",
                  "diskSizeGB": 100,
                  "managedDisk": {
                      "id": "/subscriptions/0a4f2b9c-***-***-***-40b17ef8c3ab/resourceGroups/europe-jm/providers/Microsoft.Compute/disks/jenkinsworkspaces"
                  }
              }
          ]
...

Установка Docker и запуск Jenkins

Для завершения провижена — требуется смонтировать диск, установить Docker и запустить Jenkins.

Для этого используем скрипт, который хранится в Azure Storage Account Blob-storage:

#!/usr/bin/env bash

sudo curl https://get.docker.com/ | bash
sudo usermod -aG docker serverdmin
mkdir /jenkins
mount /dev/sdc1 /jenkins/
docker run -tid -p 80:8080 -v /jenkins:/jenkins -v /var/run/docker.sock:/var/run/docker.sock -e JENKINS_HOME='/jenkins' --name jenkins jenkins

Для вызова этого скрипта из ARM-шаблона — используем расширение CustomScript из Microsoft.Azure.Extensions.

В resources нашего шаблона — добавляем:

...
    {
      "type": "Microsoft.Compute/virtualMachines/extensions",
      "name": "[concat(variables('vmName'),'/DockerInstall')]",
      "apiVersion": "2015-05-01-preview",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'))]"
      ],
      "properties": {
        "publisher": "Microsoft.Azure.Extensions",
        "type": "CustomScript",
        "typeHandlerVersion": "2.0",
        "autoUpgradeMinorVersion": true,
        "settings": {
          "fileUris": [
            "https://utils.blob.core.windows.net/scripts/jenkins_provision.sh"
            ],
          "commandToExecute": "bash jenkins_provision.sh"
        },
        "protectedSettings": {
          "storageAccountName": "jmutils",
          "storageAccountKey": "tOkXJ*****T66Bw=="
        }
      }
    }
...

На этом подготовка шаблона завершена.

Создаём группу:

azure group create -l westeurope -n jm-jenkins Finalizing -f jm-jenkins_v2.json -e jm-jenkins_v2.parameters.json

Логи

CustomScript сработал раза с десятого. Для поиска проблем — на созданной машине проверяем три лога:

/var/log/azure/custom-script/handler.log
/var/lib/waagent/custom-script/download/0/stdout
/var/lib/waagent/custom-script/download/0/stderr

tail -f /var/lib/waagent/custom-script/download/0/std* /var/log/azure/custom-script/handler.log
==> /var/lib/waagent/custom-script/download/0/stderr <==
194637a1b12c: Pull complete
f5405da54999: Pull complete
66c5531af30f: Pull complete
c61bd2df6e6a: Pull complete
85969c669ffc: Pull complete
a22b8d2b56ba: Pull complete
11b90bc5b10c: Pull complete
351c841a8598: Pull complete
Digest: sha256:2bfbdb2c2606a3fa036bd7c0392003cd5aeb45978cc7fa05ba14b0412b4e9a1c
Status: Downloaded newer image for jenkins:latest
==> /var/lib/waagent/custom-script/download/0/stdout <==
Experimental: false
If you would like to use Docker as a non-root user, you should now consider
adding your user to the «docker» group with something like:
sudo usermod -aG docker your-user
Remember that you will have to log out and back in for this to take effect!
ec93c63beeaeba788096fda4965a44311a88123ae77e2b1a25ae1693276e7046
==> /var/log/azure/custom-script/handler.log <==
time=2017-02-28T11:54:41Z version=v2.0.2/git@494f22e-clean operation=enable seq=0 event=»validated configuration»
time=2017-02-28T11:54:41Z version=v2.0.2/git@494f22e-clean operation=enable seq=0 event=»creating output directory» path=/var/lib/waagent/custom-script/download/0
time=2017-02-28T11:54:41Z version=v2.0.2/git@494f22e-clean operation=enable seq=0 event=»created output directory»
time=2017-02-28T11:54:41Z version=v2.0.2/git@494f22e-clean operation=enable seq=0 files=1
time=2017-02-28T11:54:41Z version=v2.0.2/git@494f22e-clean operation=enable seq=0 file=0 event=»download start»
time=2017-02-28T11:54:42Z version=v2.0.2/git@494f22e-clean operation=enable seq=0 file=0 event=»download complete» output=/var/lib/waagent/custom-script/download/0
time=2017-02-28T11:54:42Z version=v2.0.2/git@494f22e-clean operation=enable seq=0 event=»executing command» output=/var/lib/waagent/custom-script/download/0
time=2017-02-28T11:55:40Z version=v2.0.2/git@494f22e-clean operation=enable seq=0 event=»executed command» output=/var/lib/waagent/custom-script/download/0
time=2017-02-28T11:55:40Z version=v2.0.2/git@494f22e-clean operation=enable seq=0 event=enabled
time=2017-02-28T11:55:40Z version=v2.0.2/git@494f22e-clean operation=enable seq=0 event=end

И проверяем работу самого Jenkins:

curl http://np4zgtqmq5c76.westeurope.cloudapp.azure.com/
<html><head><meta http-equiv='refresh' content='1;url=/login?from=%2F'/><script>window.location.replace('/login?from=%2F');</script></head><body style='background-color:white; color:white;'>
Authentication required

Готово.

Ссылки по теме

Comparing AWS Cloudformation and Azure Resource Manager

Announcing general availability of Managed Disks and larger Scale Sets

Azure CLI: Managed Disks

About disks and VHDs for Azure Linux VMs