Jak dobrze parametryzować szablony ARM #1
Szablony Resource Managera w Azure to genialna sprawa. Ich deklaratywność pozwala łatwo zacząć opisywać infrastrukturę zamiast ją skryptować czy też co gorsza namiętnie wyklikiwać. Niestety z szablonami jest problem, a dokładniej z przykładami i materiałami, które uczą, jak je pisać. Obiekty są robione na sztywno zamiast budowane dynamiczne z parametrów lub zmiennych. Maszyny wirtualne, dyski, podsieci, karty sieciowe, NSG, ustawienia w WebApp czy Functions, aż proszą się o takie podejście do wdrażania
W praktyce ludzie wpisują to na sztywno w szablon zamiast korzystać z dobrej parametryzacji. Dobrym przykładem są podsieci w virtual network. Często spotykam się z tym, że są jawnie deklarowane w zasobach w szablonie. Poniżej jak to wygląda dla takiej konfiguracji obiektu. Czyli mamy parametry dla subnet1 (subnet1Name i subnet1AddressPrefix) i potem analogicznie dla podsieci dwa i trzy.
{
"apiVersion": "2018-02-01",
"name": "[parameters('name')]",
"type": "Microsoft.Network/virtualNetworks",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[parameters('addressPrefix')]"
]
},
"subnets": [
{
"name": "[parameters('subnet1Name')]",
"properties": {
"addressPrefix": "[parameters('subnet1AddressPrefix')]"
}
},
{
"name": "[parameters('subnet2Name')]",
"properties": {
"addressPrefix": "[parameters('subnet2AddressPrefix')]"
}
},
{
"name": "[parameters('subnet2Name')]",
"properties": {
"addressPrefix": "[parameters('subnet2AddressPrefix')]"
}
}
]
}
}
O wiele lepiej wygląda to jeśli użyjemy tablicy obiektów jako parametr. Jest czytelniej, prościej do parametryzacji i nie wymaga edycji samego szablonu, jeśli coś zmieniamy. Dodatkowo kopiuj/wklej jest prostsze jeżeli chcemy użyć tego w innym miejscu.
[
{
"name": "Subnet1",
"cidr": "172.16.1.0/24"
},
{
"name": "Subnet2",
"cidr": "172.16.2.0/24"
}
]
Żeby wykorzystać takie tablice obiektów należy skorzystać z funkcji copy
. W opisanym podejściu jest to copy
we właściwościach obiektu. W miejscu, gdzie ma być zbudowana lista obiektów wstawiamy copy
. W tym wypadku funkcja przyjmuje 3 parametry.
- name - w tym wypadku jest to nazwa obiektu jaki generujmy. Po tej nazwie również dostajemy się do pozycji w jakiej jest aktualnie
copy
korzystając z funkcjicopyIndex
. - count - Ile razy obiekt ma zostać skopiowany/wygenerowany. Jako, że używamy tablicy to najprościej jest użyć długości tablicy. Służy do tego funkcja
length
, która jako parametr przyjmuję tablicę. Użycie jej tolength(parameters('subnets'))
. - input - to co zostanie wygenerowane przez funkcje, o tym dalej.
Tak wygląda użycie copy
do generowania podsieci.
{
"apiVersion": "2018-02-01",
"name": "[parameters('name')]",
"type": "Microsoft.Network/virtualNetworks",
"location": "[parameters('location')]",
"properties": {
"addressSpace": "[variables('addressPrefixes')]",
"copy": [
{
"name": "subnets",
"count": "[length(parameters('subnets'))]",
"input": {
"name": "[parameters('subnets')[copyIndex('subnets')].name]",
"properties": {
"addressPrefix": "[parameters('subnets')[copyIndex('subnets')].cidr]"
}
}
}
]
}
}
input to obiekt, który w wyniku działania copy
ma się pojawić w wygenerowanym jsonie. Generacja jego może się odbywać z wykorzystaniem całego dobrodziejstwa inwentarzu dostępnego w składni szablonów, czyli funkcji, zmiennych, parametrów czy właściwości innych obiektów. W przypadku, kiedy naszym wejściem jest tablica, to wystarczy się po prostu odwołać do aktualnej pozycji funkcji copy
i po tym wybrać interesujący nas element z tablicy. Do aktualnej pozycji dostajemy się poprzez copyIndex
, czyli kiedy nasze copy
nazywa się subnets to wywołanie wygląda tak - copyIndex('subnets’)
.
Mając pozycje trzeba odwołać się do tablicy. Jak w prawie każdym języku robimy to <tablica>[<index>]
, czyli parameters('subnets')[copyIndex('subnets')]
i w ten sposób uzyskujemy dostęp do elementu w tablicy.
Jako, ze w tablicy mamy obiekty to odwojujemy się do konkretnej właściwość po kropce, czyli parameters('subnets')[copyIndex('subnets')].<nazwa właściwości>
. Dla przykładu odwołanie się do nazwy to parameters('subnets')[copyIndex('subnets')].name
.
input z copy
prezentuje się tak:
"input": {
"name": "[parameters('subnets')[copyIndex('subnets')].name]",
"properties": {
"addressPrefix": "[parameters('subnets')[copyIndex('subnets')].cidr]"
}
}
Obiekt subnets zostanie rozwinięty do takiego stanu i wdrożony.
"subnets": [
{
"name": "Subnet1",
"properties": {
"addressPrefix": "172.16.1.0/24"
}
},
{
"name": "Subnet2",
"properties": {
"addressPrefix": "172.16.2.0/24"
}
}
]
Kolejną rzeczą do takiej dynamicznej parametryzacji jest addressSpace. addressSpace posiada właściwość addressPrefixes, która jest listą adresacji dla wirtualnej sieci. W pierwszym przykładzie jest on definiowany jako jeden element na stałe. Dodanie nowej adresacji w takiej konfiguracji wymaga za każdym razem edycji szablonu.
"addressSpace": {
"addressPrefixes": [
"[parameters('addressPrefix')]"
]
}
Fajnie jest adresację przekazać jako tablice.
[ "172.16.0.0/16", "10.0.0.0/16" ]
Niestety nie można przypisać o tak sobie tablicy z parametru czy zmiennej w przypadku niektórych właściwości (takim przypadkiem jest virtual network i addressSpace) i trzeba zrobić drobny hack z użyciem obiektu.
Obiekt przygotowujemy w zmiennej addressPrefixes. W takim obiekcie można już przypisać tablice.
"variables": {
"addressPrefixes" : {
"addressPrefixes" : "[parameters('addressPrefixs')]"
}
}
Użycie przygotowanej zmiennej we właściwościach sieci.
"addressSpace": "[variables('addressPrefixes')]",
Wygenerowany zostanie z tego taki szablon w trakcie wdrożenia
"addressSpace": {
"addressPrefixes": [
"172.16.0.0/16",
"10.0.0.0/16"
]
}
Dokumentacja użycia copy wraz z przykładami
Na koniec drobne ogłoszenie. W najbliższych miesiącach planuje organizować lub współorganizować dwa wydarzenia w społeczności związane z nauka ARM. Jeśli chcesz być powiadomiony możesz zapisać się na listę tutaj albo czekać na informację na LinkedIn, Twitter czy Facebooku na mojej stronie lub też polskiej grupie Azure.