Post

Azure DevOps pipeline Bicep-el

Azure-ban erőforrást sok féle képpen létrehozhatunk, de bizonyos idő után biztosan eljutunk az Infrastructure as code (IaC) megoldásokig. Ha már rendelkezésünkre áll a kellő tudás ahhoz, hogy kész infra kódokat írjunk, akkor jöhetnek a pipeline megoldások, amikor a központi tárból nem csak magát az alkalmazást hozhatjuk létre, de a hozzá tartozó infrastruktúrát is. Ebben a postban lépésről lépésre végigmegyek egy bicep-es VM környezet létrehozásán az Azure DevOps-al.

Előfeltételek

Környezet létrehozásához a következőkre van szükség:

  • Service Principal (SP)
  • Azure DevOps regisztráció
  • Azure előfizetés amibe van pár $
  • Ingyenes DevOps esetén, ha még MSDN se áll rendelkezsére, lehet igényelni ingyenes Parallel job-ot (legalul leírom hogyan)

Azure DevOps projekt létrehozása

Első lépésként hozzunk létre egy üres projektet, melyben tároljuk a szükséges fájlokat, illetve ahonnan tudjuk majd futtatni a pipeline-t. http://dev.azure.com

img-description
img-description

Ezzel el is készült az üres projekt, most hozzunk létre egy service connection-t, mely segítségével erőforrásokat hozhatunk létre az előfizetésünkben.

Service connection létrehozása

Ezt már egy előző postban leírtam, hogy hogyan is kell létrehozni, mely itt található.

Bicep fileok létrehozása

A példa feladatban egy egyszerű VM-et fogunk létrehozni, melyet a Microsoft Bicep példatárából másoltam ki. Kicsit módosítottam rajta, hogy támogassa az ismételt futtatásokat is. Ezek a példa Bicep file-ok itt találhatóak.

Hozzuk létre a Bicep mappába a két szükséges file-t.

img-description
img-description
img-description
img-description

Illesszük be a következő kódot:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
targetScope = 'subscription'

@description('Name of the resourceGroup to create')
param rgName string = 'rgname'

@description('Location for the resourceGroup')
param rgLocation string = 'westeurope'

@description('adminUsername')
param adminUsername string

@description('adminPasswordOrKey')
@secure()
param adminPasswordOrKey string

resource newRg 'Microsoft.Resources/resourceGroups@2019-10-01' = {
  name: rgName
  location: rgLocation
  tags: {
    Note: 'subscription level deployment'
  }
  properties: {}
}

module vm './vm.bicep' = {
  name: 'vm'
  scope: newRg
  params: {
    adminUsername: adminUsername
    adminPasswordOrKey: adminPasswordOrKey
  }
}

img-description

Ezután hozzuk létre a második fájlt is.

img-description
img-description
img-description

Illesszük be a következő kódot:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
@description('The name of you Virtual Machine.')
param vmName string = 'simpleLinuxVM'

@description('Username for the Virtual Machine.')
param adminUsername string

@description('Type of authentication to use on the Virtual Machine. SSH key is recommended.')
@allowed([
  'sshPublicKey'
  'password'
])
param authenticationType string = 'password'

@description('SSH Key or password for the Virtual Machine. SSH key is recommended.')
@secure()
param adminPasswordOrKey string

@description('Unique DNS Name for the Public IP used to access the Virtual Machine.')
param dnsLabelPrefix string = toLower('simplelinuxvm-${uniqueString(resourceGroup().id)}')

@description('The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version.')
@allowed([
  '12.04.5-LTS'
  '14.04.5-LTS'
  '16.04.0-LTS'
  '18.04-LTS'
])
param ubuntuOSVersion string = '18.04-LTS'

@description('Location for all resources.')
param location string = resourceGroup().location

@description('The size of the VM')
param vmSize string = 'Standard_B4ms'

@description('Name of the VNET')
param virtualNetworkName string = 'vNet'

@description('Name of the subnet in the virtual network')
param subnetName string = 'Subnet'

@description('Name of the Network Security Group')
param networkSecurityGroupName string = 'SecGroupNet'

var publicIPAddressName = '${vmName}PublicIP'
var networkInterfaceName = '${vmName}NetInt'
var osDiskType = 'Standard_LRS'
var subnetAddressPrefix = '10.1.0.0/24'
var addressPrefix = '10.1.0.0/16'
var linuxConfiguration = {
  disablePasswordAuthentication: true
  ssh: {
    publicKeys: [
      {
        path: '/home/${adminUsername}/.ssh/authorized_keys'
        keyData: adminPasswordOrKey
      }
    ]
  }
}

resource nic 'Microsoft.Network/networkInterfaces@2020-06-01' = {
  name: networkInterfaceName
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          subnet: {
            id: resourceId('Microsoft.Network/virtualNetworks/subnets', virtualNetworkName, subnetName) 
          }
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: publicIP.id
          }
        }
      }
    ]
  }
}

resource nsg 'Microsoft.Network/networkSecurityGroups@2020-06-01' = {
  name: networkSecurityGroupName
  location: location
  properties: {
    securityRules: [
      {
        name: 'SSH'
        properties: {
          priority: 1000
          protocol: 'Tcp'
          access: 'Allow'
          direction: 'Inbound'
          sourceAddressPrefix: '*'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '22'
        }
      }
    ]
  }
}

resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = {
  name: virtualNetworkName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        addressPrefix
      ]
    }
    subnets: [
      {
        name: subnetName
        properties: {
          addressPrefix: subnetAddressPrefix
          networkSecurityGroup: {
          id: nsg.id
          }
        }
      }
    ]
  }
}


resource publicIP 'Microsoft.Network/publicIPAddresses@2020-06-01' = {
  name: publicIPAddressName
  location: location
  sku: {
    name: 'Basic'
  }
  properties: {
    publicIPAllocationMethod: 'Dynamic'
    publicIPAddressVersion: 'IPv4'
    dnsSettings: {
      domainNameLabel: dnsLabelPrefix
    }
    idleTimeoutInMinutes: 4
  }
}

resource vm 'Microsoft.Compute/virtualMachines@2020-06-01' = {
  name: vmName
  location: location
  properties: {
    hardwareProfile: {
      vmSize: vmSize
    }
    storageProfile: {
      osDisk: {
        createOption: 'FromImage'
        managedDisk: {
          storageAccountType: osDiskType
        }
      }
      imageReference: {
        publisher: 'Canonical'
        offer: 'UbuntuServer'
        sku: ubuntuOSVersion
        version: 'latest'
      }
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: nic.id
        }
      ]
    }
    osProfile: {
      computerName: vmName
      adminUsername: adminUsername
      adminPassword: adminPasswordOrKey
      linuxConfiguration: ((authenticationType == 'password') ? null : linuxConfiguration)
    }
  }
}

output adminUsername string = adminUsername
output hostname string = publicIP.properties.dnsSettings.fqdn
output sshCommand string = 'ssh ${adminUsername}@${publicIP.properties.dnsSettings.fqdn}'

img-description

Pipeline létrehozása

Létrehoztunk minden szükséges Bicep file-t az előző részben, így a következő lépésként már magát a pipeline-t hozzuk létre, mely alkalmazza majd a fájlainkat.
Hozzuk is létre a pipeline-t:

img-description
img-description
img-description
img-description

A gyakorlás kedvéért 2 változót hozzunk létre, mely a géphez szükséges felhasználónevet és jelszót fogja átadni a gépnek.

img-description
img-description
img-description
img-description
img-description
img-description

Töröljünk ki mindent az automatikus feltöltött file-ból, majd illesszük be a következő tartalmat:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#Nincs automatikus indítás, csak kézi
trigger: none

pool:
  vmImage: ubuntu-latest

variables:
#Cseréljük ki arra, amit létrehoztunk
  ServiceConnectionName: 'Land3'

stages:
#LintCode-ban ellenőrizzük, hogy nincs e szintaktikai hiba a fájlban
  - stage:
    displayName: LintCode
    jobs:
    - job: LintCode
      displayName: Lint code
      steps:
        - script: |
            az bicep build --file Bicep/main.bicep
          name: LintBicepCode
          displayName: Run Bicep linter         
  - stage:
#Validálás alatt már a tartalmat is ellenőrizzük, ez már az Azure Resource Manager szinten 
    displayName: Validate
    jobs:
    - job: ValidateBicepCode
      displayName: Validate Bicep code
      steps:
        - task: AzureCLI@2
          name: RunPreflightValidation
          displayName: Run preflight validation
          inputs:
            azureSubscription: $(ServiceConnectionName)
            scriptType: 'bash'
            scriptLocation: 'inlineScript'
            inlineScript: |
              az deployment sub validate \
                --template-file Bicep/main.bicep \
                --location=westeurope \
                --parameters adminUsername=$(ADMINUSERNAME) adminPasswordOrKey=$(ADMINPASSWORDORKEY)
  - stage:
#Deploy alatt tényleges "telepítés történik"   
    displayName: Deploy
    jobs:
      - job: DeployBicepcode
        steps:
          - task: AzureCLI@2
            inputs:
              azureSubscription: $(ServiceConnectionName)
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                az deployment sub create \
                --template-file Bicep/main.bicep \
                --location=westeurope \
                --parameters adminUsername=$(ADMINUSERNAME) adminPasswordOrKey=$(ADMINPASSWORDORKEY)

img-description
ServiceConnectionName: ‘Land3’ résznél cseréljük ki a ‘Land3’-at arra a névre, amilyen néven létrehoztuk a service connection-t

img-description
img-description

Ha mindent jól csináltunk, akkor létre is jön a gépünk Azure-ban a hozzá tartozó erőforrásokkal együtt. img-description

!Fontos, ha már nem kell amit létrehoztunk, akkor töröljük azt ;)

Ingyenes Azure DevOps parallel

Amennyiben még nem használtuk ez a környezet, és ezt a hibát tapasztaljuk:
img-description
Úgy igényelni kell ezen a formon keresztül futtató környezetet: Link
Pár óra után engedélyezik és már mehet is a tesztelés.(Régen automatikus volt, de gondolom páran visszaéltek vele…)

This post is licensed under CC BY 4.0 by the author.