Automatisering af cloud-ressourcer med Python og Pulumi: Grundlæggende provisionering

Kombinering af imperativ og deklarativ cloud-automatisering med Python og Pulumi. Safespring er en cloudplatform bygget på OpenStack.

Jarle Bjørgeengen

Jarle Bjørgeengen

Former Chief Product Officer

Denne tekst er automatisk oversat for din bekvemmelighed. Du kan læse teksten på:

.

Effektiv udnyttelse af cloud-tjenester handler om at automatisere op- og nedskalering af ressourcer i takt med de konstant skiftende behov. Listen over værktøjer, deres egenskaber og deres egnethed til formålet kan være overvældende.

Oven i det kommer det skiftende landskab for licenser og abonnementer, nogle gange med forbløffende effekter på din eksisterende cloud-strategi, senest eksemplificeret af Hashicorps uventede licensændring til Business Source License (BSL). I dette landskab skal vi altid være parate til at tilpasse os og ændre, så det er godt at have kendskab til både veletablerede værktøjer og nogle alternativer. Dette indlæg er det første i en serie om, hvordan man udnytter Pulumi til at automatisere tjenesteforbrug mod Safesprings cloud-API’er.

Forudsætninger

API-adgang

API’erne er bag en firewall, så enten skal du arbejde fra en instans (jumphost), som allerede befinder sig i et Safespring-datacenter (så er firewallåbningerne allerede på plads). Hvis ikke, skal du sende en e-mail til support@safespring.com og bede om at få whitelistet kilde-IP’en for den vært/CIDR, du vil bruge Pulumi fra.

Kommandoen curl ifconfig.me fortæller dig din offentlige IP, som den ses af API-firewallen. Dette er især nyttigt, hvis du tilgår API’et via NAT. Fortæl os også, hvis IP-adressen/-adresserne ændrer sig, så vi kan åbne den nye og lukke den gamle.

Opsætning af Pulumi

Vi anbefaler den manuelle installation til Linux, fordi vi fraråder at sende indhold fra internettet direkte til en skal. Den manuelle installation er naturligvis også let at automatisere i overensstemmelse med jeres sikkerhedspolitikker.

Når du har en fungerende Pulumi-eksekverbar på din lokale maskine (i dette tilfælde en Ubuntu 22.04 jumphost), skal du logge ind på Pulumi SaaS for at gemme spor af dine ressourcer og deres historik. I et senere blogindlæg ser vi, hvordan man ændrer stack-lagringen til en Safespring S3-bucket og dermed kører en selvstændig opsætning af Pulumi.

ubuntu@demo-jumphost:~$ pulumi login
Manage your Pulumi stacks by logging in.
Run `pulumi login --help` for alternative login options.
Enter your access token from https://app.pulumi.com/account/tokens
    or hit <ENTER> to log in using your browser                   :


  Welcome to Pulumi!

  Pulumi helps you create, deploy, and manage infrastructure on any cloud using
  your favorite language. You can get started today with Pulumi at:

      https://www.pulumi.com/docs/get-started/

  Tip: Resources you create with Pulumi are given unique names (a randomly
  generated suffix) by default. To learn more about auto-naming or customizing resource
  names see https://www.pulumi.com/docs/intro/concepts/resources/#autonaming.


Logged in to pulumi.com as JarleB (https://app.pulumi.com/JarleB)
ubuntu@demo-jumphost:~$

Inden vi fortsætter, skal vi installere en afhængighed til Pulumi OpenStack Python-projektskabelonen:

$ sudo apt update && sudo apt install python3.8-venv

Opret derefter et nyt Pulumi-projekt, søg efter OpenStack, og vælg openstack-python:

ubuntu@demo-jumphost:~/pulumi$ pulumi new
Please choose a template (37/220 shown):
 opens  [Use arrows to move, type to filter]
  openstack-go                       A minimal OpenStack Go Pulumi program
  openstack-javascript               A minimal OpenStack JavaScript Pulumi program
> openstack-python                   A minimal OpenStack Python Pulumi program
  openstack-typescript               A minimal OpenStack TypeScript Pulumi program
  openstack-yaml                     A minimal OpenStack Pulumi YAML program


project name: (pulum) pulumi-demo
project description: (A minimal OpenStack Python Pulumi program)
Created project 'pulumi-demo'

Please enter your desired stack name.
To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`).
stack name: (dev)
Created stack 'dev'

Installing dependencies...

Creating virtual environment...
Finished creating virtual environment
Updating pip, setuptools, and wheel in virtual environment...
Collecting pip
(...)

Næste skridt er at sætte de nødvendige miljøvariabler og/eller clouds.yaml konfigurationsfilen. Foreløbig er den enkleste måde at logge ind i OpenStacks webgrænseflade (Horizon) og downloade OpenStack RC-filen fra menuen Project -> API access -> Download OpenStack RC file.

Kør derefter source på filen:

ubuntu@demo-jumphost:~/pulumi$ source ~/.sandbox.safespring.com-openrc.sh
Please enter your OpenStack Password for project sandbox.safespring.com as user jarle@safespring.com:
ubuntu@demo-jumphost:~/pulumi$

Du kan også bruge applikationslegitimationsoplysninger, hvis du ikke vil afsløre din personlige adgangskode i miljøvariablerne.

På nuværende tidspunkt er vi klar til at begynde at oprette ressourcer ved hjælp af et Pulumi-program skrevet i Python. Pulumi-programmer skal dog kende nogle parametre for at vedligeholde ressourcerne. Den nemmeste måde hurtigt at få disse oplysninger på er at bruge OpenStack CLI. OpenStack CLI bruger allerede det indlæste miljø som konfiguration, så vi behøver blot at installere OpenStack CLI for at kunne bruge kommandoen openstack til dette formål.

ubuntu@demo-jumphost:~/pulumi$ sudo apt-get install virtualenvwrapper
source /usr/share/virtualenvwrapper/virtualenvwrapper.sh
mkvirtualenv oscli
(oscli) ubuntu@demo-jumphost:~/pulumi$ pip install --upgrade piping
(oscli) ubuntu@demo-jumphost:~/pulumi$ pip install python-openstackclient python-neutronclient
(oscli) ubuntu@demo-jumphost:~/pulumi$ openstack token issue
+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Field      | Value                                                                                                                                                                                   |
+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| expires    | 2023-08-25T18:54:44+0000
(...)

Kommandoen token issue bekræfter, at OpenStack CLI er konfigureret korrekt i forhold til OpenStack API’et. Pulumi vil bruge den samme konfiguration.

Oprettelse af en instans med Python/Pulumi

Efter at have konfigureret Pulumi med OpenStack-skabelonen, ser vi en fil kaldet __main__.py i Pulumi-arbejdsmappen. Dette er en eksempelfil, der er oprettet af skabelonprocessen.

"""An OpenStack Python Pulumi program"""

import pulumi
from pulumi_openstack import compute

# Create an OpenStack resource (Compute Instance)
instance = compute.Instance('test',
	flavor_name='s1-2',
	image_name='Ubuntu 16.04')

# Export the IP of the instance
pulumi.export('instance_ip', instance.access_ip_v4)

For at oprette en instans i Safespring skal vi blot erstatte de eksempelparametre med værdier, der passer til Safespring-platformen. Du kan bruge OpenStack CLI til at få disse oplysninger.

For at få en liste over tilgængelige instansstørrelser skal du bruge:

(oscli) ubuntu@demo-jumphost:~/pulumi$ openstack flavor list
+--------------------------------------+----------------+-------+------+-----------+-------+-----------+
| ID                                   | Name           |   RAM | Disk | Ephemeral | VCPUs | Is Public |
+--------------------------------------+----------------+-------+------+-----------+-------+-----------+
| 02e49e88-dfd3-4a41-b6f6-5c95ef8364cf | b2.c16r32      | 32768 |    0 |         0 |    16 | True      |
| 117ccf62-1758-4300-ab1b-b5ba78948346 | l2.c8r16.100   | 16384 |  100 |         0 |     8 | True      |
| 121bfee4-8d70-4668-81b6-0a755e022fd1 | l2.c16r32.500  | 32768 |  500 |         0 |    16 | True      |
| 1a58ba25-8444-4fb9-a2a8-4f8027cd0f59 | l2.c8r16.1000  | 16384 | 1000 |         0 |     8 | True      |
| 1da5b6f6-d722-4e83-b507-2d4ea6d3ca7d | l2.c16r32.1000 | 32768 | 1000 |         0 |    16 | True      |
| 52d06354-6cd0-42fa-a7e8-2998647db65d | l2.c2r4.1000   |  4096 | 1000 |         0 |     2 | True      |
| 52f4bc1c-973b-409e-aea1-23f7e2d14b27 | b2.c2r4        |  4096 |    0 |         0 |     2 | True      |
| 5748eadf-a45f-41f3-ba4e-dd03973ceed3 | b2.c1r4        |  4096 |    0 |         0 |     1 | True      |
| 601cf092-db6d-4faa-b456-0e8613a0c9dc | l2.c2r4.500    |  4096 |  500 |         0 |     2 | True      |
| 62502c5c-9441-4546-8859-c243a506da31 | b2.c2r8        |  8192 |    0 |         0 |     2 | True      |
| 79a8fc06-7385-490f-86b7-4daf178b6590 | l2.c16r32.100  | 32768 |  100 |         0 |    16 | True      |
| 7cade287-87a1-4bdf-a92b-a4208101895d | b2.c1r2        |  2048 |    0 |         0 |     1 | True      |
| 8574373f-b266-400c-80b1-49027c97bdcb | l2.c32r64.1000 | 65536 | 1000 |         0 |    32 | True      |
| 8f84ceab-89c1-4dfb-9ef6-97504475bd3a | l2.c4r8.500    |  8192 |  500 |         0 |     4 | True      |
| 9268de17-2d5b-4885-bc53-155f093aed6d | l2.c2r4.100    |  4096 |  100 |         0 |     2 | True      |
| a697753c-12ef-4abf-8c1d-f3bef761ffb7 | l2.c4r8.100    |  8192 |  100 |         0 |     4 | True      |
| b10d4f41-6ca4-4dae-8fec-7580cdd2a1dd | b2.c8r32       | 32768 |    0 |         0 |     8 | True      |
| b4d75d91-f3b4-4ad1-b859-0306064856d8 | l2.c8r16.500   | 16384 |  500 |         0 |     8 | True      |
| d2fc99a7-85da-49dd-9725-6670086a1aa9 | b2.c16r64      | 65536 |    0 |         0 |    16 | True      |
| e91ff4b7-cf9e-4d95-8374-aa3b1d765200 | l2.c4r8.1000   |  8192 | 1000 |         0 |     4 | True      |
| eb1d6bec-60ab-4a6b-95e9-313e33dd6712 | b2.c4r8        |  8192 |    0 |         0 |     4 | True      |
| f448fae2-135d-4865-a8d3-8306cc1a119e | b2.c4r16       | 16384 |    0 |         0 |     4 | True      |
| f578ce2f-2a60-4803-8274-a4b92a44a227 | b2.c8r16       | 16384 |    0 |         0 |     8 | True      |
+--------------------------------------+----------------+-------+------+-----------+-------+-----------+
(oscli) ubuntu@demo-jumphost:~/pulumi$

For at få en liste over tilgængelige billeder skal du bruge:

(oscli) ubuntu@demo-jumphost:~/pulumi$ openstack image list
+--------------------------------------+------------------------------------------------+--------+
| ID                                   | Name                                           | Status |
+--------------------------------------+------------------------------------------------+--------+
| d007510b-908a-44a5-a1a5-b2dc8e751260 | debian-10                                      | active |
| f2ef69eb-2856-4319-95f5-902f43fccef8 | debian-11                                      | active |
| a7394047-8f79-4b2c-92aa-d4a818ef42c2 | debian-12                                      | active |
| ee81e161-d04c-40c0-a848-582750f9903b | ubuntu-18.04                                   | active |
| cfc0ca97-780b-4fa5-aa87-44e4f41a0766 | ubuntu-20.04                                   | active |
| aac74808-9dba-4f49-a530-70a23b4163f3 | ubuntu-22.04                                   | active |
+--------------------------------------+------------------------------------------------+--------+
(oscli) ubuntu@demo-jumphost:~/pulumi$

Du kan også uploade dit eget billede.

Og til sidst for netværk:

(oscli) ubuntu@demo-jumphost:~/pulumi$ openstack network list
+--------------------------------------+------------------+----------------------------------------------------------------------------+
| ID                                   | Name             | Subnets                                                                    |
+--------------------------------------+------------------+----------------------------------------------------------------------------+
| 14ff54e0-80e4-492b-a54a-8c4d4097ed8f | default          | 1ae2aebe-542a-410a-8fea-bf1f941d6d6a, 34489b94-634a-45cd-bac9-61deea3daf5a |
| 33dc493f-f4d5-4ab4-bf8e-43bee3faf3ef | public           | 368db41d-c77f-4759-8113-d702818702fd, 5d1e4008-7a1a-4c88-9b0c-7d0faf54a9d8 |
| 67892ac3-1dcd-4bba-bd60-28b5d037f6ff | private          | 059d94a0-0fc1-40dd-9814-eb00571c6a4d, 6ff36feb-deb9-4cc0-aa09-8007006988bb |
+--------------------------------------+------------------+----------------------------------------------------------------------------+

Så vælger vi at ændre __main__.py-filen sådan her:

"""An OpenStack Python Pulumi program"""

import pulumi
from pulumi_openstack import compute

# Create an OpenStack resource (Compute Instance)
instance = compute.Instance('pulumi-demo',
	flavor_name='l2.c2r4.100',
	networks=[{"name": "public"}],
	image_name='ubuntu-22.04')

# Export the IP of the instance
pulumi.export('instance_ip', instance.access_ip_v4)

Bemærk, at vi kun tilknytter ét netværk, selvom det teknisk set er muligt at tilknytte flere netværk, idet parameteren networks er af typen liste. Det skyldes, at der på Safespring-platformen ikke er behov for at tilknytte flere netværksinterfaces; det kan endda skabe ustabilitet og problemer. For at læse, hvorfor det forholder sig sådan, kan du læse blogindlægget om Safesprings netværksmodel

Til sidst kan vi køre pulumi up for at opbygge ressourcegrafen og derefter anvende den graf på OpenStack API’et med Pulumi.

(oscli) ubuntu@demo-jumphost:~/pulumi$

(oscli) ubuntu@demo-jumphost:~/pulumi$ pulumi up
Previewing update (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/JarleB/pulumi-demo/dev/previews/ec8d68fc-15a6-4dd3-8add-0b5076170bfd

     Type                           Name             Plan
     pulumi:pulumi:Stack            pulumi-demo-dev
 +   └─ openstack:compute:Instance  pulumi-demo      create


Outputs:
  + instance_ip: output<string>

Resources:
    + 1 to create
    1 unchanged

Do you want to perform this update? yes
Updating (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/JarleB/pulumi-demo/dev/updates/3

     Type                           Name             Status
     pulumi:pulumi:Stack            pulumi-demo-dev
 +   └─ openstack:compute:Instance  pulumi-demo      created (15s)


Outputs:
  + instance_ip: "212.162.146.151"

Resources:
    + 1 created
    1 unchanged

Duration: 17s

(oscli) ubuntu@demo-jumphost:~/pulumi$

Så lad os se, om instanserne blev oprettet ved hjælp af OpenStack CLI’en:

(oscli) ubuntu@demo-jumphost:~/pulumi$ openstack server list |grep pulu
| 94593df2-eae9-40cf-bfba-7f8078bae970 | pulumi-demo-0d3a61e                   | ACTIVE  | public=212.162.146.151, 2a09:d400:0:1::1d9 | ubuntu-22.04             | l2.c2r4.100  |
(oscli) ubuntu@demo-jumphost:~/pulumi$

Og selvfølgelig var det det! Bemærk, at eftersom vi ikke angav et navn, oprettede Pulumi et for os med pulumi-demo som præfiks og en tilfældig streng som suffiks.

Hvis navnet på instansen er vigtigt for os, kan vi bare angive det i Pulumi-programmet sådan her:

"""An OpenStack Python Pulumi program"""

import pulumi
from pulumi_openstack import compute

# Create an OpenStack resource (Compute Instance)
instance = compute.Instance('pulumi-demo',
    name = 'pulumi-demo',              # < ---- here
	flavor_name='l2.c2r4.100',
	networks=[{"name": "public"}],
	image_name='ubuntu-22.04')


# Export the IP of the instance
pulumi.export('instance_ip', instance.access_ip_v4)

Og så anvender vi ændringen:

(oscli) ubuntu@demo-jumphost:~/pulumi$ pulumi up
Previewing update (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/JarleB/pulumi-demo/dev/previews/7a022268-94e2-4f79-9b20-9f0010563b57

     Type                           Name             Plan       Info
     pulumi:pulumi:Stack            pulumi-demo-dev
 ~   └─ openstack:compute:Instance  pulumi-demo      update     [diff: ~__defaults,name]


Resources:
    ~ 1 to update
    1 unchanged

Do you want to perform this update? details
  pulumi:pulumi:Stack: (same)
    [urn=urn:pulumi:dev::pulumi-demo::pulumi:pulumi:Stack::pulumi-demo-dev]
    ~ openstack:compute/instance:Instance: (update)
        [id=94593df2-eae9-40cf-bfba-7f8078bae970]
        [urn=urn:pulumi:dev::pulumi-demo::openstack:compute/instance:Instance::pulumi-demo]
        [provider=urn:pulumi:dev::pulumi-demo::pulumi:providers:openstack::default_3_13_3::4cf9816e-9eb8-447f-9c15-4d5614b3c329]
      ~ name             : "pulumi-demo-0d3a61e" => "pulumi-demo"

Do you want to perform this update? yes
Updating (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/JarleB/pulumi-demo/dev/updates/5

     Type                           Name             Status           Info
     pulumi:pulumi:Stack            pulumi-demo-dev
 ~   └─ openstack:compute:Instance  pulumi-demo      updated (2s)     [diff: ~__defaults,name]


Outputs:
    instance_ip: "212.162.146.151"

Resources:
    ~ 1 updated
    1 unchanged

Duration: 4s

(oscli) ubuntu@demo-jumphost:~/pulumi$

Og nu blev navnet ændret:

(oscli) ubuntu@demo-jumphost:~/pulumi$ openstack server list |grep pulu
| 94593df2-eae9-40cf-bfba-7f8078bae970 | pulumi-demo                           | ACTIVE  | public=212.162.146.151, 2a09:d400:0:1::1d9 | ubuntu-22.04             | l2.c2r4.100  |
(oscli) ubuntu@demo-jumphost:~/pulumi$

Opret forbindelse til instansen

For at få adgang til instansen skal vi dog åbne nogle porte ved hjælp af sikkerhedsgrupper og regler.

Lad os tilføje denne kode til Pulumi Python-programmet:

"""An OpenStack Python Pulumi program"""

import pulumi
from pulumi_openstack import compute
from pulumi_openstack import networking

sg = networking.SecGroup('pulumi-sg',
        name = 'pulumi-sg')

ssh_rule = networking.SecGroupRule("pulumi-ssh-ingress",
    direction="ingress",
    ethertype="IPv4",
    protocol="tcp",
    port_range_min=22,
    port_range_max=22,
    remote_ip_prefix="0.0.0.0/0",
    security_group_id=sg.id)

instance = compute.Instance('pulumi-demo',
        name = 'pulumi-demo',
        flavor_name='l2.c2r4.100',
        networks=[{"name": "public"}],
        security_groups=[sg.name],           # <- New parameter for security group membership
        image_name='ubuntu-22.04')

Og kør derefter pulumi up igen:

(oscli) ubuntu@demo-jumphost:~/pulumi$ pulumi up
Previewing update (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/JarleB/pulumi-demo/dev/previews/5ee8bfa0-7cf3-4e54-9c38-534de190f776

     Type                                  Name                Plan       Info
     pulumi:pulumi:Stack                   pulumi-demo-dev
 +   ├─ openstack:networking:SecGroup      pulumi-sg           create
 ~   ├─ openstack:compute:Instance         pulumi-demo         update     [diff: ~securityGroups]
 +   └─ openstack:networking:SecGroupRule  pulumi-ssh-ingress  create


Resources:
    + 2 to create
    ~ 1 to update
    3 changes. 1 unchanged

Do you want to perform this update? yes
Updating (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/JarleB/pulumi-demo/dev/updates/17

     Type                                  Name                Status              Info
     pulumi:pulumi:Stack                   pulumi-demo-dev
 +   ├─ openstack:networking:SecGroup      pulumi-sg           created (1s)
 +   ├─ openstack:networking:SecGroupRule  pulumi-ssh-ingress  created (0.57s)
 ~   └─ openstack:compute:Instance         pulumi-demo         updated (4s)        [diff: ~securityGroups]


Resources:
    + 2 created
    ~ 1 updated
    3 changes. 1 unchanged

Duration: 8s

(oscli) ubuntu@demo-jumphost:~/pulumi$

Nu kan vi oprette forbindelse via port 22:

(oscli) ubuntu@demo-jumphost:~/pulumi$ openstack server list |grep pul
| be203992-22ce-4f60-900b-d3b47b39262f | pulumi-demo                           | ACTIVE  | public=212.162.147.112, 2a09:d400:0:1::321 | ubuntu-22.04             | l2.c2r4.100  |

(oscli) ubuntu@demo-jumphost:~/pulumi$ nc -w 1 212.162.147.112 22
SSH-2.0-OpenSSH_8.9p1 Ubuntu-3
(oscli) ubuntu@demo-jumphost:~/pulumi$

Resumé

Vi har set, at vi kan bruge Python (eller andre imperative programmeringssprog) til at udrulle og vedligeholde infrastrukturressourcer på en måde, der minder om Terraform. En ting, man dog skal være opmærksom på, er blandingen mellem deklarative og imperative tilgange. I en vis forstand opfører Pulumi-programmet sig som en sekventiel række af opgaver, selv om det til sidst opbygger en lignende ressourcegraf (tilstand) som Terraform. Det betyder, at hvis vi ændrer rækkefølgen i programmet ved at placere oprettelsen af instansen før oprettelsen af sikkerhedsgruppen, vil det fejle, fordi definitionen af den sikkerhedsgruppe, som instansen skal være medlem af, endnu ikke er defineret i programsekvensen. Dette er anderledes i Terraform, hvor rækkefølgen af kodelinjerne er helt irrelevant.

I det næste blogindlæg skalerer vi op og flytter programkonfigurationsdataene ud i yaml-filer.

Referencer