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

Вы можете обратиться к следующему репозиторию GitHub для получения более подробной информации. Мы сосредоточимся на том, как вы можете выполнить развертывание, используя Bicep.
Одна из целей использования Bicep Language — повысить гибкость при создании инфраструктуры как кода для Azure.
Если вы прочтете приведенный выше репозиторий GitHub и шаблоны ARM, вы заметите, что это немного сложный шаблон ARM.
Используя Bicep, вы упростите объявление ресурсов, которые хотите развернуть в Azure.
Предпосылки
Для развертывания решения вам потребуется следующее:
- Активная учетная запись Azure: вы можете создать учетную запись бесплатно.
- Azure Bicep установлен на вашей локальной машине.
- Азур PowerShell. См.: Установка Azure PowerShell.
- Группа ресурсов в вашей подписке 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 здесь.
-Дэйв Р.