Azure DevOps pipeline Terraformal
Azure-ban erőforrást sok féle képpen létrehozhatunk, de bizonyos idő után biztos 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, jöhetnek a pipeline megoldások, amikor is a központi tárból nem csak magát az alkalmazást hozhatjuk létre, de a hozzá tartozó infrastruktúrát is. Ebbe a postban lépésről lépésre végigmegyek egy Terraformos-os 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)
Storage Account Terraform számára
A Terraform-nak szüksége van egy helyre, ahova a state file-t mentheti, illetve a későbbiekben hozzá is férhet. Hozzunk létre egy storage account-ot, amit direkt erre a célra fogunk használni.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
az login
#Ha több előfizetéshez van jogosultsága a felhasználónknak
az account list
az account set --subscription "Előfizetés_neve"
#----------------------------------------------------------
$RESOURCE_GROUP_NAME="terraform_rg"
#egyedi névre van szükség régió szinten
$STORAGE_ACCOUNT_NAME="terraform_sa_3f37edh3"
# Resource Group létrehozása
az group create -l westeurope -n $RESOURCE_GROUP_NAME
# Storage Account létrehozása
az storage account create -n $STORAGE_ACCOUNT_NAME -g $RESOURCE_GROUP_NAME -l westeurope --sku Standard_LRS
# Storage Account blob létrehozása
az storage container create --name tfstate --account-name $STORAGE_ACCOUNT_NAME
Azure DevOps projekt létrehozása
Hozzunk létre egy üres projektet, amelyben eltároljuk a szükséges konfigurációs file-okat, illetve ahonnan tudjuk majd futtatni a pipeline-t. http://dev.azure.com
Ezzel el is készült az üres projekt, most pedig hozzuk létre egy service connection-t, amely 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ó.
Terraform file létrehozása
A példa feladatban egy egyszerű VM-et fogunk létrehozni, amelyet a Microsoft Docs-ból másoltam ki. Eredeti verzió itt található.
Hozzuk létre a Terraform mappába a Terraform file-t.
Illesszük be a következőt:
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
# Configure the Microsoft Azure Provider
provider "azurerm" {
# The "feature" block is required for AzureRM provider 2.x.
# If you're using version 1.x, the "features" block is not allowed.
version = "~>2.0"
features {}
}
terraform {
backend "azurerm" {}
}
data "azurerm_client_config" "current" {}
# Create a resource group if it doesn't exist
resource "azurerm_resource_group" "myterraformgroup" {
name = "myResourceGroup2"
location = "westeurope"
tags = {
environment = "Terraform Demo"
}
}
# Create virtual network
resource "azurerm_virtual_network" "myterraformnetwork" {
name = "myVnet"
address_space = ["10.0.0.0/16"]
location = "eastus"
resource_group_name = azurerm_resource_group.myterraformgroup.name
tags = {
environment = "Terraform Demo"
}
}
# Create subnet
resource "azurerm_subnet" "myterraformsubnet" {
name = "mySubnet"
resource_group_name = azurerm_resource_group.myterraformgroup.name
virtual_network_name = azurerm_virtual_network.myterraformnetwork.name
address_prefixes = ["10.0.1.0/24"]
}
# Create public IPs
resource "azurerm_public_ip" "myterraformpublicip" {
name = "myPublicIP"
location = "eastus"
resource_group_name = azurerm_resource_group.myterraformgroup.name
allocation_method = "Dynamic"
tags = {
environment = "Terraform Demo"
}
}
# Create Network Security Group and rule
resource "azurerm_network_security_group" "myterraformnsg" {
name = "myNetworkSecurityGroup"
location = "eastus"
resource_group_name = azurerm_resource_group.myterraformgroup.name
security_rule {
name = "SSH"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
tags = {
environment = "Terraform Demo"
}
}
# Create network interface
resource "azurerm_network_interface" "myterraformnic" {
name = "myNIC"
location = "eastus"
resource_group_name = azurerm_resource_group.myterraformgroup.name
ip_configuration {
name = "myNicConfiguration"
subnet_id = azurerm_subnet.myterraformsubnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.myterraformpublicip.id
}
tags = {
environment = "Terraform Demo"
}
}
# Connect the security group to the network interface
resource "azurerm_network_interface_security_group_association" "example" {
network_interface_id = azurerm_network_interface.myterraformnic.id
network_security_group_id = azurerm_network_security_group.myterraformnsg.id
}
# Generate random text for a unique storage account name
resource "random_id" "randomId" {
keepers = {
# Generate a new ID only when a new resource group is defined
resource_group = azurerm_resource_group.myterraformgroup.name
}
byte_length = 8
}
# Create storage account for boot diagnostics
resource "azurerm_storage_account" "mystorageaccount" {
name = "diag${random_id.randomId.hex}"
resource_group_name = azurerm_resource_group.myterraformgroup.name
location = "eastus"
account_tier = "Standard"
account_replication_type = "LRS"
tags = {
environment = "Terraform Demo"
}
}
# Create (and display) an SSH key
resource "tls_private_key" "example_ssh" {
algorithm = "RSA"
rsa_bits = 4096
}
output "tls_private_key" {
value = tls_private_key.example_ssh.private_key_pem
sensitive = true
}
# Create virtual machine
resource "azurerm_linux_virtual_machine" "myterraformvm" {
name = "myVM"
location = "eastus"
resource_group_name = azurerm_resource_group.myterraformgroup.name
network_interface_ids = [azurerm_network_interface.myterraformnic.id]
size = "Standard_DS2_v2"
os_disk {
name = "myOsDisk"
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "18.04-LTS"
version = "latest"
}
computer_name = "myvm"
admin_username = "azureuser"
disable_password_authentication = true
admin_ssh_key {
username = "azureuser"
public_key = tls_private_key.example_ssh.public_key_openssh
}
boot_diagnostics {
storage_account_uri = azurerm_storage_account.mystorageaccount.primary_blob_endpoint
}
tags = {
environment = "Terraform Demo"
}
}
Pipeline létrehozása
Létrehoztuk a szükséges terraform file-t az előző részben, a következő lépésként már magát a pipeline-t hozzuk létre, amely alkalmazza majd azt.
Hozzuk is létre a pipeline-t:
Töröljünk ki mindent az automatikus feltöltött file-ból, majd illesszük be a következőt:
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
trigger: none
pool:
vmImage: ubuntu-latest
variables:
#Service connacetion neve amit létrehoztunk:
- name: backendServiceArm
value: 'Land3'
#RG neve amit létrehoztunk a state file számára
- name: backendAzureRmResourceGroupName
value: 'gudszenttf'
#SA neve amit létrehoztunk a state file számára
- name: backendAzureRmStorageAccountName
value: 'gudszenttf'
#Container neve amit létrehoztunk a state file számára az SA-n belül
- name: backendAzureRmContainerName
value: 'tfstatedevops'
- name: backendAzureRmKey
value: 'terraform.tfstate'
- name: terraform_version
value: '0.15.4'
stages :
- stage: validate
jobs:
- job: validate
continueOnError: false
steps:
- task: TerraformInstaller@0
displayName: 'install'
inputs:
terraformVersion: ${{variables.terraform_version}}
- task: TerraformTaskV2@2
displayName: 'init'
inputs:
provider: 'azurerm'
command: 'init'
backendServiceArm: ${{variables.backendServiceArm }}
backendAzureRmResourceGroupName: ${{variables.backendAzureRmResourceGroupName }}
backendAzureRmStorageAccountName: ${{variables.backendAzureRmStorageAccountName }}
backendAzureRmContainerName: ${{variables.backendAzureRmContainerName }}
backendAzureRmKey: ${{variables.backendAzureRmKey }}
workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform'
- task: TerraformTaskV2@2
displayName: 'validate'
inputs:
provider: 'azurerm'
command: 'validate'
- stage: plan
dependsOn: [validate]
condition: succeeded('validate')
jobs:
- job: terraform_plan_develop
steps:
- task: TerraformInstaller@0
displayName: 'install'
inputs:
terraformVersion: ${{variables.terraform_version}}
- task: TerraformTaskV2@2
displayName: 'init'
inputs:
provider: 'azurerm'
command: 'init'
backendServiceArm: ${{variables.backendServiceArm }}
backendAzureRmResourceGroupName: ${{variables.backendAzureRmResourceGroupName }}
backendAzureRmStorageAccountName: ${{variables.backendAzureRmStorageAccountName }}
backendAzureRmContainerName: ${{variables.backendAzureRmContainerName }}
backendAzureRmKey: ${{variables.backendAzureRmKey }}
workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform'
- task: TerraformTaskV2@2
displayName: 'plan'
inputs:
provider: 'azurerm'
command: 'plan'
environmentServiceNameAzureRM: ${{variables.backendServiceArm }}
workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform'
- stage: apply
dependsOn: [plan]
condition: succeeded('plan')
jobs:
- job: terraform_apply_develop
steps:
- task: TerraformTaskV2@2
displayName: 'init'
inputs:
provider: 'azurerm'
command: 'init'
backendServiceArm: ${{variables.backendServiceArm }}
backendAzureRmResourceGroupName: ${{variables.backendAzureRmResourceGroupName }}
backendAzureRmStorageAccountName: ${{variables.backendAzureRmStorageAccountName }}
backendAzureRmContainerName: ${{variables.backendAzureRmContainerName }}
backendAzureRmKey: ${{variables.backendAzureRmKey }}
workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform'
- task: TerraformTaskV2@2
displayName: 'apply'
inputs:
provider: 'azurerm'
command: 'apply'
environmentServiceNameAzureRM: ${{variables.backendServiceArm }}
workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform'
Commentek alapján cseréljük ki a változókat
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.
!Fontos, ha már nem kell, amit korábban létrehoztunk, akkor töröljük is azt ;)
Ingyenes Azure DevOps parallel
Amennyiben még nem használtuk ez a környezet, és ezt a hibát tapasztaljuk:
Ú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…)