В этой статье показано, как развернуть кластер Docker Swarm в Azure с помощью Infrastructure-As-Code с Azure Bicep, доменным языком (DSL) для декларативного развертывания ресурсов Azure.

Docker Swarm — это инструмент оркестрации контейнеров, позволяющий управлять несколькими контейнерами, развернутыми на нескольких хост-компьютерах.

Следующий шаблон Bicep основан на следующей архитектуре:

Вы можете обратиться к следующему репозиторию GitHub для получения более подробной информации. Мы сосредоточимся на том, как вы можете выполнить развертывание, используя Bicep.

Одна из целей использования Bicep Language — повысить гибкость при создании инфраструктуры как кода для Azure.

Если вы прочтете приведенный выше репозиторий GitHub и шаблоны ARM, вы заметите, что это немного сложный шаблон ARM.

Используя Bicep, вы упростите объявление ресурсов, которые хотите развернуть в Azure.

Предпосылки

Для развертывания решения вам потребуется следующее:

Давайте начнем!

Обзор решения

Мы создадим шаблон Bicep, который создаст 3 менеджера Swarm и указанное количество узлов Swarm в расположении группы ресурсов.

Решение будет включать следующие файлы:

  • main.bicep: это шаблон бицепса.
  • azuredeploy.parameters.json: эти файлы параметров содержат значения, используемые для развертывания шаблона Bicep.

1. Создайте пару ключей SSH

Первый шаг — создать пару ключей SSH; вы можете прочитать следующую статью о том, как создать пару ключей SSH для виртуальных машин Linux в Azure — https://docs.microsoft.com/en-us/azure/virtual-machines/linux/mac-create-ssh-keys »

В этом случае мы создадим пару ключей SSH с помощью консоли Azure Bash. На портале Azure запросите новую консоль, как показано на изображении ниже.

Облачная оболочка — портал Azure

Затем мы сгенерируем SSH-ключ, используя следующую команду:

ssh-keygen \
    -m PEM \
    -t rsa \
    -b 4096 \
    -C "docker-swarm" \
    -f ~/.ssh/docker-swarm-priv-key \
    -N yourpasshphrase

Это сгенерирует ключ в каталоге SSH в вашем файловом ресурсе:

Если вы не знакомы с форматом открытого ключа SSH, вы можете отобразить свой открытый ключ с помощью следующей команды cat, заменив «~/.ssh/id_rsa.pub» на путь и имя файла. вашего собственного файла открытого ключа, если необходимо:

cat ~/.ssh/docker-swarm-priv-key.pub

Приведенная выше команда покажет открытый ключ SSH в консоли. Он понадобится нам во время развертывания, так что держите его под рукой.

Прохладный! Теперь у нас есть пара ключей SSH. Мы передадим значение этого ключа SSH в файле параметров.

2. Шаблон Azure Bicep — параметры

Создайте новый файл в своем рабочем каталоге и назовите его «main.bicep». Определим следующие параметры:

@description('SSH public key for the Virtual Machines.')
@secure()
param sshPublicKey string
@description('Number of Swarm worker nodes in the cluster.')
param nodeCount int = 3
@description('Location for all resources.')
param location string = resourceGroup().location
@description('Admin Username.')
param adminUsername string = 'azureuser'
@description('Default Master VM Size')
param vmSizeMaster string = 'Standard_A0'
@description('Default Node VM Size')
param vmSizeNode string = 'Standard_A2'

3. Шаблон Azure Bicep — переменные

Мы определим следующие переменные:

var masterCount = 3
var vmNameMaster_var = 'swarm-master-'
var vmNameNode_var = 'swarm-node-'
var availabilitySetMasters_var = 'swarm-masters-set'
var availabilitySetNodes_var = 'swarm-nodes-set'
var osImagePublisher = 'Canonical'
var osImageOffer = 'UbuntuServer'
var osImageSKU = '16.04-LTS'
var managementPublicIPAddrName_var = 'swarm-lb-masters-ip'
var nodesPublicIPAddrName_var = 'swarm-lb-nodes-ip'
var virtualNetworkName_var = 'swarm-vnet'
var subnetNameMasters = 'subnet-masters'
var subnetNameNodes = 'subnet-nodes'
var addressPrefixMasters = '10.0.0.0/16'
var addressPrefixNodes = '192.168.0.0/16'
var subnetPrefixMasters = '10.0.0.0/24'
var subnetPrefixNodes = '192.168.0.0/24'
var mastersNsgName_var = 'swarm-masters-firewall'
var nodesNsgName_var = 'swarm-nodes-firewall'
var newStorageAccountName_var = uniqueString(resourceGroup().id, deployment().name)
var clusterFqdn = 'swarm-${uniqueString(resourceGroup().id, deployment().name)}'
var storageAccountType = 'Standard_LRS'
var mastersLbName_var = 'swarm-lb-masters'
var mastersLbIPConfigName = 'MastersLBFrontEnd'
var mastersLbBackendPoolName = 'swarm-masters-pool'
var nodesLbName_var = 'swarm-lb-nodes'
var nodesLbBackendPoolName = 'swarm-nodes-pool'
var sshKeyPath = '/home/${adminUsername}/.ssh/authorized_keys'
var consulServerArgs = [
  '-advertise 10.0.0.4 -bootstrap-expect 3 -retry-join 10.0.0.5 -retry-join 10.0.0.6'
  '-advertise 10.0.0.5 -retry-join 10.0.0.4 -retry-join 10.0.0.6'
  '-advertise 10.0.0.6 -retry-join 10.0.0.4 -retry-join 10.0.0.5'
]

4. Шаблон Azure Bicep — ресурсы

Мы определим следующие ресурсы:

resource newStorageAccountName 'Microsoft.Storage/storageAccounts@2021-01-01' = {
  name: newStorageAccountName_var
  location: location
  sku: {
    name: storageAccountType
  }
  kind: 'StorageV2'
}
resource availabilitySetMasters 'Microsoft.Compute/availabilitySets@2017-12-01' = {
  name: availabilitySetMasters_var
  location: location
  sku: {
    name: 'Aligned'
  }
  properties: {
    platformFaultDomainCount: 2
    platformUpdateDomainCount: 5
  }
}
resource availabilitySetNodes 'Microsoft.Compute/availabilitySets@2017-12-01' = {
  name: availabilitySetNodes_var
  location: location
  sku: {
    name: 'Aligned'
  }
  properties: {
    platformFaultDomainCount: 2
    platformUpdateDomainCount: 5
  }
}
resource managementPublicIPAddrName 'Microsoft.Network/publicIPAddresses@2015-06-15' = {
  name: managementPublicIPAddrName_var
  location: location
  properties: {
    publicIPAllocationMethod: 'Dynamic'
    dnsSettings: {
      domainNameLabel: '${clusterFqdn}-manage'
    }
  }
}
resource nodesPublicIPAddrName 'Microsoft.Network/publicIPAddresses@2015-06-15' = {
  name: nodesPublicIPAddrName_var
  location: location
  properties: {
    publicIPAllocationMethod: 'Dynamic'
    dnsSettings: {
      domainNameLabel: clusterFqdn
    }
  }
}
resource virtualNetworkName 'Microsoft.Network/virtualNetworks@2015-06-15' = {
  name: virtualNetworkName_var
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        addressPrefixMasters
        addressPrefixNodes
      ]
    }
    subnets: [
      {
        name: subnetNameMasters
        properties: {
          addressPrefix: subnetPrefixMasters
          networkSecurityGroup: {
            id: mastersNsgName.id
          }
        }
      }
      {
        name: subnetNameNodes
        properties: {
          addressPrefix: subnetPrefixNodes
          networkSecurityGroup: {
            id: nodesNsgName.id
          }
        }
      }
    ]
  }
}
resource mastersNsgName 'Microsoft.Network/networkSecurityGroups@2015-06-15' = {
  name: mastersNsgName_var
  location: location
  properties: {
    securityRules: [
      {
        name: 'ssh'
        properties: {
          protocol: 'Tcp'
          sourcePortRange: '*'
          destinationPortRange: '22'
          sourceAddressPrefix: '*'
          destinationAddressPrefix: '*'
          access: 'Allow'
          priority: 1000
          direction: 'Inbound'
        }
      }
    ]
  }
}
resource nodesNsgName 'Microsoft.Network/networkSecurityGroups@2015-06-15' = {
  name: nodesNsgName_var
  location: location
  properties: {
    securityRules: [
      {
        name: 'AllowAny'
        properties: {
          description: 'Swarm node ports need to be configured on the load balancer to be reachable'
          protocol: '*'
          sourcePortRange: '*'
          destinationPortRange: '*'
          sourceAddressPrefix: '*'
          destinationAddressPrefix: '*'
          access: 'Allow'
          priority: 1000
          direction: 'Inbound'
        }
      }
    ]
  }
}
resource vmNameMaster_nic 'Microsoft.Network/networkInterfaces@2015-06-15' = [for i in range(0, masterCount): {
  name: '${vmNameMaster_var}${i}-nic'
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipConfigMaster'
        properties: {
          privateIPAllocationMethod: 'Static'
          privateIPAddress: '10.0.0.${(i + 4)}'
          subnet: {
            id: resourceId('Microsoft.Network/virtualNetworks/subnets', virtualNetworkName_var, subnetNameMasters)
          }
          loadBalancerBackendAddressPools: [
            {
              id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', mastersLbName_var, mastersLbBackendPoolName)
            }
          ]
          loadBalancerInboundNatRules: [
            {
              id: resourceId('Microsoft.Network/loadBalancers/inboundNatRules', mastersLbName_var, 'SSH-${vmNameMaster_var}${i}')
            }
          ]
        }
      }
    ]
  }
  dependsOn: [
    mastersLbName
    virtualNetworkName
    mastersLbName_SSH_vmNameMaster
  ]
}]
resource mastersLbName 'Microsoft.Network/loadBalancers@2015-06-15' = {
  name: mastersLbName_var
  location: location
  properties: {
    frontendIPConfigurations: [
      {
        name: mastersLbIPConfigName
        properties: {
          publicIPAddress: {
            id: managementPublicIPAddrName.id
          }
        }
      }
    ]
    backendAddressPools: [
      {
        name: mastersLbBackendPoolName
      }
    ]
  }
}
resource mastersLbName_SSH_vmNameMaster 'Microsoft.Network/loadBalancers/inboundNatRules@2021-03-01' = [for i in range(0, masterCount): {
  name: '${mastersLbName_var}/SSH-${vmNameMaster_var}${i}'
  properties: {
    frontendIPConfiguration: {
      id: resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', mastersLbName_var, mastersLbIPConfigName)
    }
    protocol: 'Tcp'
    frontendPort: (i + 2200)
    backendPort: 22
    enableFloatingIP: false
  }
  dependsOn: [
    mastersLbName
  ]
}]
resource nodesLbName 'Microsoft.Network/loadBalancers@2015-06-15' = {
  name: nodesLbName_var
  location: location
  properties: {
    frontendIPConfigurations: [
      {
        name: 'LoadBalancerFrontEnd'
        properties: {
          publicIPAddress: {
            id: nodesPublicIPAddrName.id
          }
        }
      }
    ]
    backendAddressPools: [
      {
        name: nodesLbBackendPoolName
      }
    ]
  }
}
resource vmNameNode_nic 'Microsoft.Network/networkInterfaces@2015-06-15' = [for i in range(0, nodeCount): {
  name: '${vmNameNode_var}${i}-nic'
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipConfigNode'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          subnet: {
            id: resourceId('Microsoft.Network/virtualNetworks/subnets', virtualNetworkName_var, subnetNameNodes)
          }
          loadBalancerBackendAddressPools: [
            {
              id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', nodesLbName_var, nodesLbBackendPoolName)
            }
          ]
        }
      }
    ]
  }
  dependsOn: [
    nodesLbName
    virtualNetworkName
  ]
}]
resource vmNameMaster 'Microsoft.Compute/virtualMachines@2017-03-30' = [for i in range(0, masterCount): {
  name: '${vmNameMaster_var}${i}'
  location: location
  properties: {
    availabilitySet: {
      id: availabilitySetMasters.id
    }
    hardwareProfile: {
      vmSize: vmSizeMaster
    }
    osProfile: {
      computerName: '${vmNameMaster_var}${i}'
      adminUsername: adminUsername
      linuxConfiguration: {
        disablePasswordAuthentication: true
        ssh: {
          publicKeys: [
            {
              path: sshKeyPath
              keyData: sshPublicKey
            }
          ]
        }
      }
    }
    storageProfile: {
      imageReference: {
        publisher: osImagePublisher
        offer: osImageOffer
        sku: osImageSKU
        version: 'latest'
      }
      osDisk: {
        name: '${vmNameMaster_var}${i}_OSDisk'
        caching: 'ReadWrite'
        createOption: 'FromImage'
      }
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: resourceId('Microsoft.Network/networkInterfaces', '${vmNameMaster_var}${i}-nic')
        }
      ]
    }
  }
  dependsOn: [
    newStorageAccountName
    availabilitySetMasters
  ]
}]
resource vmNameNode 'Microsoft.Compute/virtualMachines@2017-03-30' = [for i in range(0, nodeCount): {
  name: '${vmNameNode_var}${i}'
  location: location
  properties: {
    availabilitySet: {
      id: availabilitySetNodes.id
    }
    hardwareProfile: {
      vmSize: vmSizeNode
    }
    osProfile: {
      computerName: '${vmNameNode_var}${i}'
      adminUsername: adminUsername
      linuxConfiguration: {
        disablePasswordAuthentication: true
        ssh: {
          publicKeys: [
            {
              path: sshKeyPath
              keyData: sshPublicKey
            }
          ]
        }
      }
    }
    storageProfile: {
      imageReference: {
        publisher: osImagePublisher
        offer: osImageOffer
        sku: osImageSKU
        version: 'latest'
      }
      osDisk: {
        name: '${vmNameNode_var}${i}_OSDisk'
        caching: 'ReadWrite'
        createOption: 'FromImage'
      }
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: resourceId('Microsoft.Network/networkInterfaces', '${vmNameNode_var}${i}-nic')
        }
      ]
    }
  }
  dependsOn: [
    newStorageAccountName
  ]
}]
resource vmNameMaster_DockerExtension 'Microsoft.Compute/virtualMachines/extensions@2015-06-15' = [for i in range(0, masterCount): {
  name: '${vmNameMaster_var}${i}/DockerExtension'
  location: location
  properties: {
    publisher: 'Microsoft.Azure.Extensions'
    type: 'DockerExtension'
    typeHandlerVersion: '1.0'
    autoUpgradeMinorVersion: true
    settings: {
      compose: {
        consul: {
          image: 'progrium/consul'
          command: '-server -node master${i} ${consulServerArgs[i]}'
          ports: [
            '8500:8500'
            '8300:8300'
            '8301:8301'
            '8301:8301/udp'
            '8302:8302'
            '8302:8302/udp'
            '8400:8400'
          ]
          volumes: [
            '/data/consul:/data'
          ]
          restart: 'always'
        }
        swarm: {
          image: 'swarm'
          command: 'manage --replication --advertise ${reference(resourceId('Microsoft.Network/networkInterfaces', '${vmNameMaster_var}${i}-nic')).ipConfigurations[0].properties.privateIPAddress}:2375 --discovery-opt kv.path=docker/nodes consul://10.0.0.4:8500'
          ports: [
            '2375:2375'
          ]
          links: [
            'consul'
          ]
          volumes: [
            '/etc/docker:/etc/docker'
          ]
          restart: 'always'
        }
      }
    }
  }
  dependsOn: [
    vmNameMaster  
  ]
}]
resource vmNameNode_DockerExtension 'Microsoft.Compute/virtualMachines/extensions@2015-06-15' = [for i in range(0, nodeCount): {
  name: '${vmNameNode_var}${i}/DockerExtension'
  location: location
  properties: {
    publisher: 'Microsoft.Azure.Extensions'
    type: 'DockerExtension'
    typeHandlerVersion: '1.0'
    autoUpgradeMinorVersion: true
    settings: {
      docker: {
        port: '2375'
        options: [
          '--cluster-store=consul://10.0.0.4:8500'
          '--cluster-advertise=eth0:2375'
        ]
      }
    }
  }
  dependsOn: [
    vmNameNode
  ]
}]

5. Выходы

Мы включим следующие выходы:

output sshTunnelCmd string = 'ssh -L 2375:swarm-master-0:2375 -N ${adminUsername}@${managementPublicIPAddrName.properties.dnsSettings.fqdn} -p 2200'
output dockerCmd string = 'docker -H tcp://localhost:2375 info'
output swarmNodesLoadBalancerAddress string = nodesPublicIPAddrName.properties.dnsSettings.fqdn
output sshMaster0 string = 'ssh ${adminUsername}@${managementPublicIPAddrName.properties.dnsSettings.fqdn} -A -p 2200'
output sshMaster1 string = 'ssh ${adminUsername}@${managementPublicIPAddrName.properties.dnsSettings.fqdn} -A -p 2201'
output sshMaster2 string = 'ssh ${adminUsername}@${managementPublicIPAddrName.properties.dnsSettings.fqdn} -A -p 2202'

6. Файл параметров

Создайте новый файл с именем «azuredeploy.parameters.json». Код ниже показывает определение файла параметров:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
      "sshPublicKey": {
        "value": "GEN-SSH-PUB-KEY"
   },
      "nodeCount": {
        "value": 3
      }
    }
  }

6. Шаблон Azure Bicep — развертывание

Мы будем использовать приведенную ниже команду для развертывания нашего шаблона Bicep:

$date = Get-Date -Format "MM-dd-yyyy"
$deploymentName = "AzInsiderDeployment"+"$date"
New-AzResourceGroupDeployment -Name $deploymentName -ResourceGroupName azinsider_demo -TemplateFile .\main.bicep -TemplateParameterFile .\azuredeploy.parameters.json -c

На изображении ниже показан предварительный просмотр развертывания:

Затем мы выполним развертывание. На изображении ниже показан результат развертывания:

Вы можете проверить развертывание на портале Azure.

Теперь вы можете подключиться к управляемым узлам Swarm по SSH, используя доменное имя или общедоступный IP-адрес swarm-lb-masters. Вы можете получить полное DNS-имя из выходных данных развертывания.

Чтобы получить доступ к рабочим узлам, вы можете получить к ним доступ с главных узлов. Дополнительные сведения см. в следующем репозитории GitHub — https://github.com/Azure/azure-quickstart-templates/tree/master/application-workloads/swarm/docker-swarm-cluster.

Полный шаблон Bicep и файл параметров можно найти по следующему URL-адресу:



👉 Присоединяйтесь к списку рассылки AzInsider здесь.

-Дэйв Р.