Azuren tehokäyttö ja ARM:n salat

ARM logo

Olen jo pitkään aikonut kirjoittaa tämän blogipostauksen, koska opastamme näitä asioita melko usein asiakkaillemme ja partnereillemme. Azure-ammattitaidon näkökulmasta olemme aika erityinen ohjelmistotalo, koska suuri osa tiimistämme työskenteli ennen AJR Solutions –aikoja Microsoftilla Azuren parissa. Microsoftin asiakkaalla saattoi usein olla jokin ongelma, jota ei ollut suoraan mahdollista ratkaista Azuren palveluiden tai työkalujen kuten Powershellin avulla. Tahtoa hyödyntää Azurea kuitenkin löytyi, ja tällöin olikin mentävä hieman pintaa syvemmälle ilman Stack Overflow -gurujen avuliaita vinkkejä.

Jotta päästään asiaan, on hyvä ensin ymmärtää hieman Azuren taustaa. Azure on ehtinyt jo reilun 10 vuoden ikään, ja palvelun historia voidaan jakaa kahteen osaan. Ensimmäistä ja toistaiseksi pidempää osaa voidaan kutsua Classic- tai ASM -ajaksi ja tuoreempaa ARM-, eli Azure Resource Manager -ajaksi. Pitkään näitä kahta käytettiin rinnakkain, ja vasta viime aikoina Classicista on päästy lähes kokonaan eroon. Classicillakin on edelleen oma käyttäjäkuntansa, mutta uudet käyttäjät ja resurssit hyödyntävät ARM:ia.

Vanha Classic oli toimiva ympäristö, kunnes palveluiden, työkalujen ja erilaisten sisäisten rajapintojen määrä alkoi kasvaa merkittävästi. Myös julkisen pilven kasvu osattiin ennakoida, ja näin ollen tunnistettiinkin todellinen tarve suunnitella järjestelmä uudelleen tukemaan kasvua ja kehitystä. Syntyi ARM. Sen etuna on yhteinen rajapinta, jolla lähes kaikki palvelut toimivat. Myös Azure portaalin, Powershellin ja monien muiden työkalujen sisäinen toteutus käyttää samaa rajapintaa tai API:a. Microsoftin partnereiden ja asiakkaiden kannalta suurin mullistus tässä kehityksessä oli kuitenkin se, että Microsoft salli kaikkien hyödyntää tätä rajapintaa omiin tarpeisiinsa. Tämä mahdollistaa esimerkiksi Azure-portaalin replikoinnin.

Mitä Azure Resource Manager API:lla sitten voi tehdä ja mitä hyötyä siitä on? Muutaman mahdollisuuden mainitakseni sillä voi säästää rahaa, helpottaa ylläpitoa ja tehdä kaikkea sellaista, mikä ei muilla tavoin onnistuisi. Kustannussäästöjä syntyy esimerkiksi ohjelmalla, joka tarpeen tullen automatisoidusti pystyttää, keskeyttää, ja tuhoaa palveluita. Kerran eräs partnerimme halusi luoda automaattisesti jokaiselle asiakkaalleen oman virtuaalikoneen, joka suoritti tietyn tehtävän ja tuhoutui tämän jälkeen. Nykyisessä Docker-maailmassa samankaltaista tapausta ei ehkä tulisi enää eteen ja ratkaisun voisi luultavasti rakentaa Powershellilläkin. Tässä tapauksessa asiakkaalla oli kuitenkin omat Linux-imaget ja Scalalla toteutettu sovellus, josta ratkaisua haluttiin alkaa muodostaa. Tilannetta ei helpottanut asiakkaan toive käyttää jClouds multicloud -rajapintaa, joka taas ei tukenut Azure ARM:ia ollenkaan. Tämän vuoksi meidän olikin aloitettava projekti rakentamalla Apache jClouds open source -projektiin Azure ARM -tuki. Näitä hommia ei enää scripteillä tehdä.

Jos tämä alkoi kuulostaa mielenkiintoiselta, kannattaa jatkaa lukemista. Havainnollistan nimittäin seuraavaksi, kuinka Azure ARM API:a pääsee käyttämään. Kaikki tämä toki selviää myös Microsoftin dokumentaatiosta, mutta blogi-postauksen lukemalla sinun ei tarvitse käyttää aikaasi perusasioiden kaivamiseen sieltä. Aloitetaan seuraavilla stepeillä:

  • Rekisteröi uusi sovellus Azure Active Directoryyn. Tätä kutsutaan service principaliksi. Ota talteen sovelluksen application ID/ client ID
  • Luo avain sovellukselle. Avainta kutsutaan client secretiksi
  • Anna sovellukselle lupa käyttää Azure -tiliäsi
  • Selvitä Azure Active Directoryn directory ID
  • Selvitä Azure -tilisi subscription ID

Esittelen seuraavaksi lyhyesti kuvien kanssa, kuinka edellä esitellyt stepit tehdään korkealla tasolla Azure -portaalissa. Vaiheiden seuraamisen jälkeen sinulla on app ID, client secret, directory ID ja subscription ID. Tässä on kaikki mitä tarvitaan, jotta voidaan autentikoitua Azure AD:ta vastaan ja saada access token Azuren ARM API:n kutsumista varten.

Aloitetaan uuden sovelluksen rekisteröinnillä. Avataan Azure Portal ja valitaan vasemmasta sivupalkista Azure Active Directory.

Tämän jälkeen avautuvasta valikosta valitaan App Registrations ja yläpalkista New App Registration. Annetaan sovellukselle nimi ja sign-on urliksi esimerkiksi http://localhost tai mikä tahansa muu arvo. Sovelluksen tyyppiin ei tarvitse koskea.

Sovelluksen luomisen jälkeen ruudussa näkyy Application ID, joka otetaan talteen.

Nyt klikataan yläpalkista Settings ja valitaan kohta Keys. Annetaan avaimelle mikä tahansa nimi ja painetaan Save, minkä jälkeen avain ilmestyy ruutuun. Otetaan se talteen.

Seuraavaksi palataan pari tasoa ylemmäs Azure Active Directory -valikkoon. Valitaan sieltä Properties ja otetaan Directory ID talteen.

Lopuksi valitaan vasemmasta palkista All Resources, etsitään sieltä Subscriptions ja otetaan talteen valitun subcrciptionin ID.

Klikataan auki subscription ja valitaan valikosta IAM ja New Role Assignment, josta haetaan käyttäjä sovelluksen nimellä. Tälle annetaan rooliksi Owner, Contributor tai Reader käyttötarpeen mukaan. Jos sovelluksella halutaan tehdä kaikkea mahdollista, on sille annettava Owner –oikeudet.

No niin! Nyt päästään itse asiaan ja tekemään ensin API-kutsuja access tokenin saamiseksi ja sitten varsinaisia ARM API -kutsuja. Näitä kutsuja voi tehdä muun muassa Postman-sovelluksella, Curl-komentorivi-työkalulla tai Node.js- ja JavaScript-koodeilla. Sekä kovana Curl- että JavaScript käyttäjänä kävin läpi kovan henkisen taistelun valitakseni tähän paremman demonstrointitavan, joten heitin lopulta noppaa ja päädyin JavaScript-esimerkkiin.

Ensin todettakoon, että meidän JavaScript käyttää HTTPS-liikenteeseen ulkoista Axios-kirjastoa. Ennen kuin päästään ajamaan koodia, pitääkin siis asentaa Node.js ja tämän jälkeen ajaa seuraava komento:

npm install axios

Kun Node.js ja Axios on asennettu, kirjoitetaan alkuun seuraavat rivit:

const Axios = require("axios")

const directoryID = "4a26b9c4-snip-e513d"
const client_secret = "LzQmDEx-snip-R49A4Nz3z0="
const app_id = "62168514-snip-51a21d41ddd2"
const sub_id = "77d1859b-snip-524fc247166f"

Muuttujien kohdalle tietysti lisätään omat credentiaalit ja ID:t. Tämän jälkeen toteutetaan getToken()-funktio, joka tekee HTTP Postin credentiaalien kanssa login.microsoftonline.com -osoitteeseen hakeakseen tokenin. Seuraavaksi tulostetaan token ruutuun. Huomaa, etten selvyyden vuoksi tee mitään virhetarkastuksia, jotta koodi pysyisi yksinkertaisena ja mahdollisimman luettavana. Perään lisätään seuraava koodi:

async function getToken() {
  const url = "https://login.microsoftonline.com/" + directoryID + "/oauth2/token?api-version=1.0"
  const body = "grant_type=client_credentials&resource=https%3A%2F%2Fmanagement.core.windows.net%2F&client_id=" + app_id + "&client_secret=" + client_secret
  const contentType = "application/x-www-form-urlencoded"
  const headers = [{'Content-Type': contentType}]
  const response = await Axios.post(url, body, headers)
  return response.data.access_token
}

async function main() {
  var token = await getToken()
  console.log(token)
}

main()

Tallennetaan koodi code.js -nimellä ja kokeillaan:

node code.js

Jos token tulostui ruutuun, jatketaan kokeilemalla HTTP Get -metodia resource groupeille ja katsotaan, saadaanko listattua JSON-formaatissa olevat resurssigroupit.


async function getResourceGroups(token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups?api-version=2016-09-01"
  const headers = {'Authorization': "Bearer " + token}
  const response = await Axios.get(url, {headers})
  return response.data.value
}

async function main() {
  var token = await getToken()
  var groups = await getResourceGroups(token)
  console.log(groups)
}

main()

Nyt kun kerran pystymme hakea tokenin ja listata groupit, voimme kokeilla myös groupien lisäämistä ja poistamista.

async function addResourceGroup(name, token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups/" + name + "?api-version=2016-09-01"
  const contentType = "application/json"
  const data = {location: "West US"}
  const response = await Axios.put(url, JSON.stringify(data), {headers: {'Authorization': "Bearer " + token, 'Content-Type': contentType}})
  return response
}

async function deleteResourceGroup(name, token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups/" + name + "?api-version=2016-09-01"
  const contentType = "application/json"
  const response = await Axios.delete(url, {headers: {'Authorization': "Bearer " + token, 'Content-Type': contentType}})
  return response
}

Lisäämisen ja poistamisen kokeilu voidaan todeta, kun ajetaan eri funktioita ja katsotaan, mitä tapahtuu. On huomattava, että esimerkiksi delete resourceGroup -toiminto kestää jonkin aikaa ja operaatiota pitää pollata ennen kuin get resourceGroups näyttää uuden groupin. Tähän on olemassa myös oma API, mutta sen käsittelyn jätämme myöhempään blogi-postaukseen. Alla näet kuitenkin kaiken tarvittavan koodin kokeiluja varten. Kun siirrytään resource groupeista vaikkapa virtuaalikoneisiin, ARM-templateihin ja muihin resursseihin, kannattaa katsoa tämän postauksen alkupuolella olevan REST-referenssin materiaalia. Kaikki toimii lopulta jokseenkin samaan tapaan kuin tämä yksinkertainen esimerkki.

const Axios = require("axios")

const directoryID = "4a26b9c4-snip-e513d"
const client_secret = "LzQmDEx-snip-R49A4Nz3z0="
const app_id = "62168514-snip-51a21d41ddd2"
const sub_id = "77d1859b-snip-524fc247166f"

async function getToken() {
  const url = "https://login.microsoftonline.com/" + directoryID + "/oauth2/token?api-version=1.0"
  const body = "grant_type=client_credentials&resource=https%3A%2F%2Fmanagement.core.windows.net%2F&client_id=" + app_id + "&client_secret=" + client_secret
  const contentType = "application/x-www-form-urlencoded"
  const headers = [{'Content-Type': contentType}]
  const response = await Axios.post(url, body, headers)
  return response.data.access_token
}

async function getResourceGroups(token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups?api-version=2016-09-01"
  const headers = {'Authorization': "Bearer " + token}
  const response = await Axios.get(url, {headers})
  return response.data.value
}

async function addResourceGroup(name, token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups/" + name + "?api-version=2016-09-01"
  const contentType = "application/json"
  const data = {location: "West US"}
  const response = await Axios.put(url, JSON.stringify(data), {headers: {'Authorization': "Bearer " + token, 'Content-Type': contentType}})
  return response
}

async function deleteResourceGroup(name, token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups/" + name + "?api-version=2016-09-01"
  const contentType = "application/json"
  const response = await Axios.delete(url, {headers: {'Authorization': "Bearer " + token, 'Content-Type': contentType}})
  return response
}

async function main() {
  var token = await getToken()
  var resp = await addResourceGroup("apitesti", token)
  var groups = await getResourceGroups(token)
  console.log(groups)
}

main()

Kun koodi on ajettu, pitäisi ruutuun tulostua jotain seuraavan kaltaista riippuen siitä, onko Azure subscriptionissa resource grouppeja.

[ { id:
     '/subscriptions/77d1859b-snip-524fc247166f/resourceGroups/ajr_dns',
    name: 'ajr_dns',
    location: 'northeurope',
    properties: { provisioningState: 'Succeeded' } },

  { id:
     '/subscriptions/77d1859b-snip-524fc247166f/resourceGroups/wordpress',
    name: 'wordpress',
    location: 'northeurope',
    properties: { provisioningState: 'Succeeded' } } ]

Nyt homma on paketissa! Jos haluat oppia lisää, kysy meiltä Azure-kursseista. Kursseilla käydään läpi Azure ARM REST API:n lisäksi paljon muitakin Azuren advanced -asioita. Entä jos et ehdi itse tällaista opettelemaan, mutta mielessä on tapa hyödyntää julkista pilveä? Ota meihin yhteyttä, niin keskustellaan sinulle sopivasta ratkaisusta!