 Имеется проект на Azure, которым я занимался большую часть последнего года (с 20-го марта 2016).
Имеется проект на Azure, которым я занимался большую часть последнего года (с 20-го марта 2016).
Пост Azure: почему никогда писался под впечатлением работы как раз на нём (да и большая часть рубрики Azure — тоже).
За год мы перенесли приложение на Umbraco CMS с MS SQL базами от другого агентства к нам (и с Ажуры на Ажур), пачку небольших WPMU сайтов (см. тут>>>) и другие сервисы — в основном на PHP, и… с переменными на немецком. Дебаг этого кода при миграции доставлял неимоверно. Весь процесс миграции должен закончится большим феерверком — запуском новой версии основного сайта производителя алкогольной продукции (завтра, 25.-го).
Текущий деплой Umbraco CMS (legacy-сайт) описан в посте Azure: GoCD и MSDeploy – деплой UmbracoCMS в Azure WebServices.
Ниже кратко описана новая инфрастуктура с некоторыми примерами. Увы — не было возможности более подробно описать все сложности, с которыми столкнулся в процессе — но несколько интересных примеров будет.
Содержание
Описание проекта
Основой проекта является веб-сайт, который раньше работал на Umbraco CMS, а сейчас мигрирует на NodeJS (а с ним ещё ~90 доменов на Azure DNS).
Новая версия приложения включает в себя 6 контейнеров, один из которых — jm-website-proxy — представляет собой локальный NGINX с реверс-прокси для реврайтов и редиректов. Кроме того — есть ещё и внешний прокси, который обслуживает все домены и приложения проекта.
Рабочее окружение включает в себя три (Dev, QA — две) ресурс-группы — в одной находятся CDN и Traffic Manager, в двух других (Blue и Green) — VMSS с Docker-Swarm:
Pusblish и Preview TM — используются для доступа к актуальной версии (Publish) и версии для редакторов (Preview). Бекенд — Contentful.
Больше всего сложностей во всём этом (и у девелоперов тоже хватило внезапных проблем) оказался Azure CDN (Verizon Premium). Из-за того, что Verizon CDN не позволяет привязать домен иначе, как через CNAME (который мы не могли использовать, т.к. на домене имеется множество субдоменов, почта и т.д., подробнее см. RFC) — пришлось выстраивать целую цепочку редиректов и SSL-терминаторов.
В результате была построена такая схема. Речь о Publish сервисе, т.к. он основной:
- Пользователь приходит на http://domain.tld
- domain.tld — направлен на внешний прокси-сервис проекта (NGINX), который выполняет переадресацию на https://www.domain.tld
- Субдомен www.domain.tld через CNAMEявляется алиасом ендпоинта CDN — jm-website-production-cdn-endpoint.azureedge.net
- У CDN в качестве Origin — указан URL Traffic Manager — jm-website-production-publish.trafficmanager.net
- У Traffic Manager — имеется два endpoint-а — Green и Blue. Green направлен на URL publish-green.domain.tld, Blue — на publish-blue.domain.tld.
 В свою очередь publish-green.domain.tld и blue черезCNAMEнаправлены на Load Balancer-ы в Green и Blue ресурс-группах, за которым находятся Swarm-ноды в VMSS. В один момент времени — только один из ендпоинтов ТМ может быть активен (тут мы и делаем Blue/Green deployment).
Если кратко — то так.
С SSL тоже оказалось не всё гладко.
Сам Azure при добавлении к CDN-профайлу CustomDomain и активации HTTPS для него — выдаёт для этого домена сертифкат от DigiCert (через письмо на admin-c контакт домена). Ожидалось — что на этом мы закрываем SSL-сессию, и дальше работаем с CDN-ендпоинтами по HTTP. Оказалось, что для работы самого CDN по HTTPS — необходимо выстроить вторую SSL-сессию — от edge location серверов Verizon до наших VMSS с приложением.
Сначала решили делать это через добавление Application Gateway, но потом всё-равно пришлось добавлять локальный NGINX как сервис в общий стек для реврайтов старых URI в новые (на внешнем NGINX это было бы сложнее, т.к. много доменов/конфигов), поэтому SSL подняли там.
SSL:
- Первый сертификат, который встречает пользователь — выдан DigiCert для домена(ов), которые подключены к Azure CDN как CustomDomains. SSL termination выполняется на CDN Edge-location сервере.
- Между Azure CDN и VMSS с нодами — второе HTTPS соединение, с самоподписанным сертификатом — CDN вполне с ними работает. SSL termination выполняется на локальном прокси-сервисе (jm-website-proxy) непосредственно на нодах.
Jenkins
Рабочее окружение Jenkins
Развёртывание новых окружений, сборка новых образов и их деплой выполняются Jenkins.
Сам Jenkins работает в Docker-контейнере, ноды с билдами запускаются тоже в контейнерах.
Jenkins работает на отдельной VM, а все его данные ($JENKINS_HOME) — хранятся на отдельном data-диске, который подключается к VM во время провижена группы.
Выглядит группа так:
(data-диск — Managed, поэтому тут не виден)
Сам шаблон можно посмотреть тут>>>.
Из интересных примеров в нём — подключение data-диска с workspaces Jenkins-a:
...
    { 
      "apiVersion": "2016-04-30-preview",
      "type": "Microsoft.Compute/virtualMachines",
      "name": "[variables('vmName')]",
      ...
          "osDisk": {
            "createOption": "FromImage"
          },
          "dataDisks": [
              {   
                  "lun": 0,
                  "createOption": "Attach",
                  "caching": "None",
                  "diskSizeGB": 100,
                  "managedDisk": {
                      "id": "/subscriptions/0a4f2b9c-***-40b17ef8c3ab/resourceGroups/europe-jm/providers/Microsoft.Compute/disks/jenkinsworkspaces"
                  }
              }
          ]
        },
И вызов скрипта для установки и запуска самого Jenkins — jenkins_provision.sh с помощью CustomScript extention для VM Azure:
...
    {
      "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": "utils",
          "storageAccountKey": "tOk***6Bw=="
        }
      }
    }
...
Сам скрипт jenkins_provision.sh:
#!/usr/bin/env bash curl https://get.docker.com/ | bash usermod -aG docker jmadmin mkdir /jenkins mount /dev/sdc1 /jenkins/ useradd jenkins usermod -a -G docker jenkins docker run -u 0 -tid -p 80:8080 \ -v /jenkins:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /usr/bin/docker:/usr/bin/docker \ -v /etc/passwd:/etc/passwd \ -e JENKINS_HOME='/var/jenkins_home' --name jenkins jenkins
В строке:
... mount /dev/sdc1 /jenkins/ ...
выполняется монтирование внешнего дата-диска «Microsoft.Compute/disks/jenkinsworkspaces«.
Вместе — шаблон и скрипт создают новую виртуальную машину, устанавливают туда Docker, подключают диск с данными Jenkins-а и запускают последнюю версию Jenkins.
Подробнее — в постах Azure: подключение дополнительного диска к VM и миграция Jenkins и Azure: Azure Resource Manager provisioning и Jenkins в Docker.
Билды Jenkins
Основная задача Jenkins — собирать образы с NodeJS приложением при изменениях в репозитории. Создание Github-вебхука описано тут>>>.
Билды описаны в Groovy-скриптах:
[simterm]
$ ls -l total 76 -rw-r--r-- 1 setevoy setevoy 254 Mar 2 17:44 build.groovy drwxr-xr-x 2 setevoy setevoy 4096 Mar 2 13:34 configs -rwxr-xr-x 1 setevoy setevoy 2362 Mar 2 11:13 deploy.sh -rw-r--r-- 1 setevoy setevoy 6447 Mar 11 13:38 docker-compose_v3.yml -rw-r--r-- 1 setevoy setevoy 2492 Mar 7 17:15 docker-compose.yml -rw-r--r-- 1 setevoy setevoy 1236 Mar 3 11:37 docker-compose.yml.origin -rw-r--r-- 1 setevoy setevoy 93 Mar 3 10:41 Dockerfile -rw-r--r-- 1 setevoy setevoy 30 Feb 21 11:20 hooktest.groovy -rw-r--r-- 1 setevoy setevoy 2319 Mar 8 14:01 jm-build.groovy -rw-r--r-- 1 setevoy setevoy 568 Mar 8 14:02 jm-cms-transform-build.groovy -rw-r--r-- 1 setevoy setevoy 394 Feb 24 16:02 jm-cms-transform-DEV-deploy.groovy -rw-r--r-- 1 setevoy setevoy 1452 Apr 7 14:31 jm-provision.groovy -rw-r--r-- 1 setevoy setevoy 779 Apr 7 15:51 jm-sw-dev-provision.groovy -rw-r--r-- 1 setevoy setevoy 881 Apr 7 14:32 jm-sw-production-provision.groovy -rw-r--r-- 1 setevoy setevoy 875 Apr 7 17:33 jm-sw-staging-provision.groovy -rw-r--r-- 1 setevoy setevoy 539 Mar 8 14:02 jm-website-build.groovy -rw-r--r-- 1 setevoy setevoy 390 Feb 24 16:02 jm-website-DEV-deploy.groovy -rw-r--r-- 1 setevoy setevoy 12 Feb 21 10:33 README.md
[/simterm]
По аналогии с сетапом из поста AWS: билд Java + Maven + Docker + Packer + Terraform — имеется один общий скрипт, который содержит функции и несколько скриптов — для каждого из приложений (все на NodeJS, все собираются практически одинаково).
Скрипт с функциями:
#!/usr/bin/env groovy
def dockerBuild (imgName='1') {
    stage ('Docker build') {
        withDockerRegistry(registry: [credentialsId: 'jm-docker-hub-jmautomation']){
            sh 'git log -n 1 > version.html'
            def appimage = docker.build("jmrakqa/${imgName}:${TAG}", "--build-arg NPM_TOKEN=${NPM_TOKEN} .")
                appimage.push()
                appimage.push('latest')
        }
    }
}
def dockerDeploy (tag='1', host='2') { 
 
    git branch: "${BRANCH}", credentialsId: 'jm-github', url: 'https://github.com/jm/website-prototype.git' 
 
    stage ('Docker deploy') { 
 
        sh "echo -e \"Deploying to the DEV: ${host}\nWith TAG=${tag}\"" 
 
        sh "./buildscripts/deploy.sh ${host} ${tag}" 
    } 
} 
 
def cleanup() { 
    sh 'echo -e "\nWiping workdir $(pwd).\n"' 
    deleteDir() 
} 
 
return this
И скрипт сборки одного из приложений:
#!/usr/bin/env groovy
node {
    ENV='dev'
    TAG = "${BRANCH}-${env.BUILD_NUMBER}"
    REPOURL = 'https://github.com/jm/jm-website.git'
    dir('buildscripts') {
        git branch: 'master', credentialsId: 'jm-github', url: 'https://github.com/jm/jm-jenkins.git'
    }
    git branch: "${BRANCH}", credentialsId: 'jm-github', url: "${REPOURL}"
    def website = load 'buildscripts/jm-build.groovy'
    website.dockerBuild('website-frontend')
    website.cleanup()
}
Скрипты из репозитория ‘https://github.com/jm/jm-jenkins.git‘ загружаются в директорию buildscripts, в а корень билд-директории — загружается код из репозитория с кодом — REPOURL = ‘https://github.com/jm/jm-website.git’.
В каждом репозитории с кодом имеется Dockerfile, в котором и описана дальнейшая сборка сервиса:
FROM node:7.5.0
ENV NODE_ENV production
ARG NPM_TOKEN="${NPM_TOKEN}"
RUN useradd --user-group --create-home --shell /bin/false app
RUN apt-get update && apt-get -y install rsync
ENV HOME=/home/app
COPY . $HOME
# https://github.com/docker/docker/issues/30110
RUN chown -R app:app $HOME/
USER app
WORKDIR $HOME
RUN pwd && ls -l
RUN npm config set //registry.npmjs.org/:_authToken=${NPM_TOKEN}
RUN npm install --production=false
RUN npm run build:production && npm prune --production
CMD ["npm", "run", "start:production"]
Передача параметров (токен для Node репозитория, например) выполняется через --build-args.
Деплой собранного образа выполняется из другого скрипта другой Jenkins-джобой:
#!/usr/bin/env groovy
node {
    git branch: "${BRANCH}", credentialsId: 'jm-github', url: 'https://github.com/jm/jm-website.git'
    dir('buildscripts') {
        git branch: 'master', credentialsId: 'jm-github', url: 'https://github.com/jm/jm-jenkins.git'
    }
    def website = load 'buildscripts/jm-build.groovy'
    website.dockerDeploy("${IMAGE_TAG}", "$DEV_HOST")
}
Собранные образы загружаются в приватный репозиторий проекта на DockerHub.
Деплой
Деплой новых образов выполняется bash-скриптом, который обновляет compose-файл на сервере и пересоздаёт стек:
#!/usr/bin/env bash
HOST="$1"
USER="jmadmin"
RSA_KEY="buildscripts/.ssh/jmadmin"
# compose to be deployed to Swarm Master
COMPOSE="buildscripts/docker-compose.yml"
# Version for the :tag for image + $TRAVIS_JOB_NUMBER
# resulted vesrion will be ~ 1.0.3-44.1
# later, use ${BRANCH}-${env.BUILD_NUMBER} now
# VERSION=$(cat package.json | grep version | cut -d":" -f 2 | sed 's/"//g' | cut -d "," -f 1) > /dev/null
TAG=$2
# 1) generate new compose with sed docker-compose.yml;
# 2) upload new Compose file docker-compose.yml to $HOST;
# 3) stop Compose;
# 4) start Compose;
me="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")"
[[ -z $HOST ]] && { echo -e "\nERROR: HOST must be specified as a first argument. Exit.\n"; exit 1; }
[[ -z $TAG ]] && { echo -e "\nERROR: TAG must be specified as a second argument. Exit.\n"; exit 1; }
[[ -e $RSA_KEY ]] && chmod 400 $RSA_KEY || { echo -e "\nERROR: RSA key $RSA_KEY not found. Exit.\n"; exit 1; }
# no need atm as using LATEST tag
compose_generate () {
    # sed -e s/CHANGEME/"$1"/g scripts/docker-compose.yml.erb > scripts/docker-compose.yml
    sed -i "s/BUILDTAG/$TAG/" $COMPOSE
}
# no need atm as using LATEST tag
compose_copy () {
    scp -P 2200 -o StrictHostKeyChecking=no -i $RSA_KEY $COMPOSE $USER@$1:/home/$USER/
}
compose_stop () {
    ssh -p 2200 -t -t -o StrictHostKeyChecking=no -i "$RSA_KEY" "$USER@$1" "bash -c '
        export DOCKER_HOST=:2375
        sudo docker-compose down --rmi all -v
    '"
}
compose_start () {
    ssh -p 2200 -t -t -o StrictHostKeyChecking=no -i "$RSA_KEY" "$USER@$1" "bash -c '
        docker-compose up -d
    '"
}
echo -e "\n[$me] $TAG deployment started at $(date) to the $HOST\n"
# no need atm as using LATEST tag
#if compose_generate $VERSION-$TRAVIS_JOB_NUMBER; then
#    echo -e "New Compose created:\n"
#    cat $COMPOSE
##else
#    echo -e "\nERROR: can not create $COMPOSE. Exit.\n"
#    exit 1
#fi
if compose_copy $HOST; then
    echo -e "\n$COMPOSE copied to the $HOST.\n"
else
    echo -e "\nERROR: can not copy $COMPOSE to the $HOST. Exit.\n"
    exit 1
fi
if compose_stop $HOST; then
    echo -e "Compose stopped.\n"
else
    echo -e "\nERROR: can not stop Compose. Exit.\n"
    exit 1
fi
if compose_start $HOST; then
    echo -e "Compose started.\n"
else
    echo -e "\nERROR: can not start Compose. Exit.\n"
    exit 1
fi
На самом деле деплой выглядит немного иначе, и скрипт другой — это старая версия, но суть та же — на одном из мастеров обновляется файл docker-compose.yml, который содержит актуальную версию образов (или просто тег `latest`), после чего на Traffic Manager-е проверяется текущая активная VMSS-нода (Blue или Green) и переключается ендпоинт. Может добавлю отдельным постом по B/G деплою на Azure с помощью Traffic Manager.
Сам docker-compose выглядит примерно так:
version: '3'
services:
  proxy:
    image: "jmakqa/jm-website-proxy:latest"
    ports:
      - "80:80"
      - "443:443"
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    depends_on:
        - web
  web:
    environment:
      - JM_enableTrustProxy=true
      - JM_trustProxyIPsCSV=uniquelocal
      - JM_basicAuthEnabled=true
      - JM_contentEndpoint=http://transform:3003/get
      - JM_contentServer=http://transform:3003
      - JM_domainPattern=publish.jm-website-sw-staging.domain.tld
      - JM_internalPort=8008
      - JM_pollIntervalMs=30000
      - JM_publicPort=8008
      - JM_purgeCdn=true
    image: "jmakqa/jm-website:jm-website-version"
    ports:
      - "8008:8008"
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    depends_on:
        - transform
...
Инфрастуктура
Описание
Далее я буду рассматривать Production, т.к. он имеет полный набор всего, что пришлось тут строить.
Как уже говорилось — рабочее окружение включает в себя три группы ресурсов — Switcher, Green и Blue.
Green и Blue — одинаковы, и включают в себя:
- виртуальную сеть с двумя подсетями — для master и nodes VMSS
- две VMSS — для master и nodes
- два Load Balancer — master и nodes
- две группы безопасности и два публичных IP-адреса
Switcher-группа — включает в себя:
- CDN-профайл
- его CDN-ендпоинт
 
- Preview Traffic Manger profile — для доступа к Preview-сервисам (с двумя ендпоинтами — Green и Blue)
- Publish Traffic Manger profile — для доступа к Publish-сервисам (с двумя ендпоинтами — Green и Blue)
CDN-ендпоинт имеет несколько добавленных CustomDomain, к которым Azure через DigiCert сам выдаёт сертификат при добавлении домена:
В качестве Origin, как говорилось — указан Publish Traffic Manager, через который CDN обращается к бекендам (Swarm-nodes VMSS с приложением) для загрузки данных.
Для доступа по HTTPS — на бекендах пришлось добавлять свои сертификаты (jm-website-proxy). Т.е. между пользователем и CDN — используется сертификат от DigiCert, а между серверами CDN и нашим VMSS — самоподписанный сертификат с нашего NGINX-а.
Развёртывание окружений
Как и в случае с Jenkins — группы ресурсов для окружений вписаны в ARM-шаблоны:
[simterm]
$ ls -l azure-infrastructure/jm-website/ total 16 drwxr-xr-x 3 setevoy setevoy 4096 Apr 7 17:12 CDN drwxr-xr-x 3 setevoy setevoy 4096 Apr 21 14:17 ENV drwxr-xr-x 2 setevoy setevoy 4096 Apr 7 16:12 NSG drwxr-xr-x 3 setevoy setevoy 4096 Apr 3 14:43 obsolete
[/simterm]
- CDN— шаблоны для рес. групп switchers
- ENV— шаблоны непосредственно рабочих окружений с Docker Swarm
- NSG— шаблоны групп безопасности (см. ниже)
Например:
[simterm]
$ ls -l azure-infrastructure/jm-website/ENV/ total 52 -rw-r--r-- 1 setevoy setevoy 1319 Apr 7 11:23 jm-website-sw-dev.parameters.json -rw-r--r-- 1 setevoy setevoy 24604 Apr 21 13:05 jm-website-sw.json -rw-r--r-- 1 setevoy setevoy 1338 Apr 3 14:43 jm-website-sw-production-blue.parameters.json -rw-r--r-- 1 setevoy setevoy 1340 Apr 7 13:02 jm-website-sw-production-green.parameters.json -rw-r--r-- 1 setevoy setevoy 1345 Apr 20 17:08 jm-website-sw-staging-blue.parameters.json -rw-r--r-- 1 setevoy setevoy 1346 Apr 20 17:08 jm-website-sw-staging-green.parameters.json -rw-r--r-- 1 root root 1322 Apr 20 17:17 jm-website-sw-test.parameters.json
[/simterm]
Тут jm-website-sw.json — сам шаблон, jm-website-sw-dev.parameters.json — файл параметров.
Для CDN — картина немного другая:
[simterm]
$ ls -l azure-infrastructure/jm-website/CDN/ total 24 -rw-r--r-- 1 setevoy setevoy 297 Apr 3 14:43 jm-website-cdn-dev.parameters.json -rw-r--r-- 1 setevoy setevoy 2243 Apr 6 16:29 jm-website-cdn.json -rw-r--r-- 1 setevoy setevoy 6522 Apr 7 12:49 jm-website-cdn-tm.json -rw-r--r-- 1 setevoy setevoy 895 Apr 3 14:43 jm-website-cdn-tm-production.parameters.json -rw-r--r-- 1 setevoy setevoy 943 Apr 7 17:12 jm-website-cdn-tm-staging.parameters.json
[/simterm]
Файл jm-website-cdn.json содержит только CDN, а файл jm-website-cdn-tm.json — CDN и Traffic Manager профайлы, для Staging и Production (на Dev и QA TM не используются, т.к. у них нет B/G деплоя).
Шаблоны можно посмотреть тут>>> и тут>>>.
Из интересного в этих шаблонах.
Во первых — сам провижен Docker Swarm. Пришлось лепить костыли, и писать свой скрипт, который разворачивает всю ферму (шаблон ACS с Docker Swarm от самого Azure оказался непригоден из-за невозможности подключить свою группу безопасности (!)).
Вызывается он так же из CustomScript extention:
...
          "extensionProfile": {
            "extensions": [
              {
                "name": "MasterSwarmInstall",
                "properties": {
                  "publisher": "Microsoft.Azure.Extensions",
                  "type": "CustomScript",
                  "typeHandlerVersion": "2.0",
                  "autoUpgradeMinorVersion": false,
                  "settings": {
                    "fileUris": [
                      "https://utils.blob.core.windows.net/scripts/swarm_master_provision.sh",
                      "https://utils.blob.core.windows.net/scripts/docker_cleanup.sh"
                    ],
                    "commandToExecute": "[concat('bash swarm_master_provision.sh ', '10.0.0.4 ', parameters('environment'))]"
                  },
                  "protectedSettings": {
                    "storageAccountName": "[parameters('storageAccountName')]",
                    "storageAccountKey": "[parameters('storageAccountKey')]"
                  }
                }
              }
            ]
          }
...
Причём IP первого мастера (‘10.0.0.4 ‘) пришлось хардкодить, т.к. вытащить IP из VMSS во время провижена оказалось… Долго, криво и муторно. Вот тут>>> парень из Azure Product team-ы пытался подсказать — но уже не было времени реализовывать.
Собственно, скрипты провижена можно посмотреть тут>>> (master) и тут>>> (nodes).
Кроме того, каждое окружение (Dev/QA/etc) имеет свой шаблон группы безопасности для нод (мастер-группа у всех одна с одним правилом — разрешить доступ по SSH).
Создаются NSG с помощью ресурса Microsoft.Resources/deployments, который загружает шаблон группы из Storage Account-а:
...
    {
      "apiVersion": "2015-01-01",
      "name": "[variables('NodesNSGname')]",
      "type": "Microsoft.Resources/deployments",
      "properties": {
        "mode": "incremental",
        "templateLink": {
          "uri": "[concat('https://utils.blob.core.windows.net/templates/jm-website-nsg-', parameters('environment'), '.json', parameters('SAStoken'))]",
          "contentVersion": "1.0.0.0"
        },
        "parameters": {
          "NodesNSGname":{"value": "[variables('NodesNSGname')]"}
        }
      }
    },
...
Подробнее — в посте Azure: ARM – подключение вложенного шаблона.
Развёртывание и обновление групп ресурсов выполняются тем же Jenkins, через Azure CLI, часть параметров — в файлах, часть — в переменных самого Jenkins.
Основной скрипт — jm-provision.groovy:
#!/usr/bin/env groovy
def templateValidate(infraUrl='1', env='2', resGroup='3', templateFile='4', paramFile='5') {
    docker.image('microsoft/azure-cli').inside('-v /var/run/docker.sock:/var/run/docker.sock') {
        git branch: "${BRANCH}", credentialsId: 'jm-github', url: "${infraUrl}"
        stage('Temlate validate') {
            sh "azure login -v -u ${AZURUSER} -p ${AZUREPASS}"
            sh "azure account set -v ${SUBSCRIPTION}"
            sh "azure group template validate -g ${resGroup} -f jm-website/ENV/${templateFile} -e jm-website/ENV/${paramFile}"
        }
    }
}
def environmentUpdate(infraUrl='1', env='2', resGroup='3', templateFile='4', paramFile='5', tag='6') {
    docker.image('microsoft/azure-cli').inside('-v /var/run/docker.sock:/var/run/docker.sock') {
        git branch: "${BRANCH}", credentialsId: 'jm-github', url: "${infraUrl}"
        stage('Environment update') {
            sh "azure login -v -u ${AZURUSER} -p ${AZUREPASS}"
            sh "azure account set -v ${SUBSCRIPTION}"
// for new environment
//            sh "azure group create -l westeurope -n ${resGroup} -f jm-website/${templateFile} -e jm-website/${paramFile}"
            sh "azure group deployment create -n ${tag} -g ${resGroup} -f jm-website/ENV/${templateFile} -e jm-website/ENV/${paramFile}"
        }
    }
}
def verify(msg='1') {
    stage 'Verify'
    input id: 'Deploy', message: "${msg}", ok: 'Deploy!'
}
return this
И скрипт апдейта Production:
#!/usr/bin/env groovy
node {
    ENV='production'
    TAG = "${env.BUILD_TAG}"
    INFRAURL = 'https://github.com/jm/azure-infrastructure.git'
    RESGROUP = "${RESGROUP}"
    TMPL = "${TMPL}"
    PARAMS = "${PARAMS}"
    dir('buildscripts') {
        git branch: 'master', credentialsId: 'jm-github', url: 'https://github.com/jm/jm-jenkins.git'
    }
    git branch: "${BRANCH}", credentialsId: 'jm-github', url: "${INFRAURL}"
    def provision = load 'buildscripts/jm-provision.groovy'
    provision.verify("WARNING: you are going to update PRODUCTION environment. Are you sure?")
    provision.templateValidate("${INFRAURL}", "${ENV}", "${RESGROUP}", "${TMPL}", "${PARAMS}")
    provision.verify("Is Verify OK? Proceed with an environment deployment?")
    provision.environmentUpdate("${INFRAURL}", "${ENV}", "${RESGROUP}", "${TMPL}", "${PARAMS}", "${TAG}")
}
В целом — это всё, попозже может быть добавлю ещё пару связанных постов, ибо проект (:cry:) ещё ни разу не закончен.








