Openstack - Básico

IaaS

Infraestrutura como Serviço, em inglês Infrastructure as a Service (IaaS) é um modelo de serviço no qual os recursos computacionais são fornecidos por um determinado provedor. Os fornecedores de IaaS fornecem muitos serviços como armazenamento, redes e virtualização liberando os consumidores do fardo de manter o hardware de uma infraestrutura localmente.

Os serviços de IaaS podem estar em uma nuvem pública (compartilhando o hardware com outros usuários), uma nuvem privada (sem compartilhamento de recursos) ou uma núvem híbrida, combinando os dois modelos.

Nesse tipo de serviço é comum existirem APIs para facilitar a criação de recursos de forma automatizada.

Openstack

Openstack é uma plataforma livre e de código aberto para computação em nuvem. É um dos três projetos de código aberto mais ativos do mundo, junto com o kernel do Linux e o navegador Chromium.

Assim como a maioria dos softwares livres, o Openstack é mantido por muitas empresas de forma simultânea, cada empresa possui sua própria versão derivada da versão “community” (chamada de upstream).

Algumas da empresas envolvidas no Openstack:

releases

Openstack Morreu?

Assim como os Mainframes, Cobol e Java, não o Openstack não está morrendo.

Casos de Uso

Componentes

Porque VMs e não Containers

Existem alguns casos em que máquinas virtuais são mais vantajosas que containeres:

DevStack

https://www.openstack.org/software/project-navigator/deployment-tools

O DevStack é um conjunto de scripts utilizados para criar um ambiente completo de Openstack através do git. Este ambiente é muito mais leve e simples que uma instalação de produção, e normalmente é utilizado para testes e estudo.

Independente do tipo de instalação utilizado, se sua máquina possui menos de 16GB, habilite o swap.

Torne-se o usuário root:

sudo -i

Execute os seguintes comandos para criar o arquivo de swap, formatá-lo, habilitá-lo e configurá-lo para montagem automática:

dd if=/dev/zero of=/swapfile bs=1M count=4096
mkswap /swapfile
chmod 0600 /swapfile
swapon /swapfile
echo '/swapfile       swap    swap    defaults 0 0' >> /etc/fstab

Configuração

Para a versão zed do Openstack, uma máquina com Ubuntu 22.04 foi utilizada.

Como o usuário root, execute os seguintes comandos:

apt-get update && apt-get install -y git
useradd -s /bin/bash -d /opt/stack -m stack
chmod +x /opt/stack
echo "stack ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/stack

cat > /etc/systemd/system/devstack-reboot-fix.service <<'EOF'
[Unit]
Description=Sobe a bridge e configura o iptables
After=ovs-vswitchd.service

[Service]
Type=oneshot
ExecStart=ip link set br-ex up
ExecStart=ip addr add 172.24.4.1/24 dev br-ex
ExecStart=iptables -t nat -A POSTROUTING -j MASQUERADE -s 172.24.4.0/24

[Install]
WantedBy=multi-user.target
EOF
systemctl enable devstack-reboot-fix.service

sudo -u stack -i

Em uma instalação single-node do DevStack temos uma infraestrutura básica e relativamente simples de provisionar. Recomenda-se 16GB para esse tipo de instalação.

Como o usuário stack executar os seguintes comandos como base:

git clone -b stable/zed https://opendev.org/openstack/devstack
cd devstack

cat > local.conf <<'EOF'
[[local|localrc]]
ADMIN_PASSWORD=openstack
DATABASE_PASSWORD=$ADMIN_PASSWORD
RABBIT_PASSWORD=$ADMIN_PASSWORD
SERVICE_PASSWORD=$ADMIN_PASSWORD
enable_plugin heat https://opendev.org/openstack/heat stable/zed
enable_service s-proxy s-object s-container s-account
EOF

Instalação

Com o arquivo definido, iniciar a instalação:

./stack.sh

Ao finalizar a instalação, podemos começar a interagir com o Openstack através de variáveis de ambiente que são configuradas pelo arquivo openrc:

source openrc

Problemas ao Reiniciar

Ao reiniciar a host do Devstack as máquinas virtuais ficarão sem acesso externo e também não poderemos acessá-las através do floating ip. Um serviço chamado devstack-reboot-fix.service foi criado para executar os seguintes comandos automaticamente após a inicialização do ovs-vswitchd:

ip link set br-ex up
ip addr add 172.24.4.1/24 dev br-ex
iptables -t nat -A POSTROUTING -j MASQUERADE -s 172.24.4.0/24

Cliente (openstack) e Painel (Horizon)

Todos os serviços do Openstack possuem seus próprios clientes, cada qual com sua própria sintaxe. Felizmente existe um cliente unificado chamado openstack que padroniza e centraliza a execução dos comandos que conversarão com os mais diversos componentes. Em algumas poucas exceções o cliente de determinados componentes precisarão ser utilizados (por exemplo a criação de “shares”).

O cliente do Openstack é bastante versátil e fornece uma sintaxe padronizada para a grande maioria dos comandos:

openstack <tipo> <ação> [parâmetros] [nome]

Por exemplo:

openstack image create --file ~/debian-11-genericcloud-amd64-20230124-1270.qcow2 debian-11

Caso tenha dúvida em algum comando, podemos verificar a saída da ajuda. Existem algumas formas e dependendo da versão a exibição pode ser diferente entre estes dois comandos:

openstack help server create
# ou
openstack server create --help

Para situações mais simples, podemos simplesmente omitir os parâmetros obrigatórios e receber uma saída útil para verificações:

openstack image create 
# output:
# usage: openstack image create [-h] [-f {json,shell,table,value,yaml}] [-c COLUMN] [--noindent] [--prefix PREFIX] [--max-width <integer>] [--fit-width] [--print-empty] [--id <id>]
#                               [--container-format <container-format>] [--disk-format <disk-format>] [--min-disk <disk-gb>] [--min-ram <ram-mb>] [--file <file> | --volume <volume>]
#                               [--force] [--progress] [--sign-key-path <sign-key-path>] [--sign-cert-id <sign-cert-id>] [--protected | --unprotected]
#                               [--public | --private | --community | --shared] [--property <key=value>] [--tag <tag>] [--project <project>] [--import] [--project-domain <project-domain>]
#                               <image-name>
# openstack image create: error: the following arguments are required: <image-name>

O formato padrão de exibição das informações do Openstack é em tabela:

openstack image list
# output:
# +--------------------------------------+---------------------------------+--------+
# | ID                                   | Name                            | Status |
# +--------------------------------------+---------------------------------+--------+
# | f05c04f2-1071-4e1f-b1c9-58f31f97c094 | Fedora-Cloud-Base-36-1.5.x86_64 | active |
# | 71bbd221-8517-4117-9eb9-e7741c0697ed | cirros-0.5.2-x86_64-disk        | active |
# +--------------------------------------+---------------------------------+--------+

Podemos modificar o resultado trocando para JSON ou YAML, o que facilita o processamento destas informações por linguagens de programação:

openstack image list -f json
# output:
# [
#   {
#     "ID": "f05c04f2-1071-4e1f-b1c9-58f31f97c094",
#     "Name": "Fedora-Cloud-Base-36-1.5.x86_64",
#     "Status": "active"
#   },
#   {
#     "ID": "71bbd221-8517-4117-9eb9-e7741c0697ed",
#     "Name": "cirros-0.5.2-x86_64-disk",
#     "Status": "active"
#   }
# ]

Muitas vezes a saída dos comandos são muito longas e acabam sendo muito difíceis de ler, para isso podemos utilizar o subcomando --fit-width ou --max-width=<N>:

openstack image show f05c04f2-1071-4e1f-b1c9-58f31f97c094 --max-width=80
# +------------------+-----------------------------------------------------------+
# | Field            | Value                                                     |
# +------------------+-----------------------------------------------------------+
# | checksum         | 7f7cdad25b77f232078bf454c39529d3                          |
# | container_format | bare                                                      |
# | created_at       | 2023-02-25T01:04:26Z                                      |
# | disk_format      | qcow2                                                     |
# | file             | /v2/images/f05c04f2-1071-4e1f-b1c9-58f31f97c094/file      |
# | id               | f05c04f2-1071-4e1f-b1c9-58f31f97c094                      |
# | min_disk         | 0                                                         |
# | min_ram          | 0                                                         |
# | name             | Fedora-Cloud-Base-36-1.5.x86_64                           |
# | owner            | 35f37247834742cfb58946b13265e4a6                          |
# | properties       | hw_rng_model='virtio', os_hash_algo='sha512', os_hash_val |
# |                  | ue='e1b41fe4e7e911c58fc90cceb30da8adfb8302e5935f2edad8b11 |
# |                  | 72f1a52c6ba3faa1f23d170aeef1f062f77bf7838bc9e8a6dd3c58dfe |
# |                  | 7278596c4899338620', os_hidden='False',                   |
# |                  | owner_specified.openstack.md5='',                         |
# |                  | owner_specified.openstack.object='images/Fedora-Cloud-    |
# |                  | Base-36-1.5.x86_64', owner_specified.openstack.sha256=''  |
# | protected        | False                                                     |
# | schema           | /v2/schemas/image                                         |
# | size             | 448266240                                                 |
# | status           | active                                                    |
# | tags             |                                                           |
# | updated_at       | 2023-02-25T01:04:31Z                                      |
# | virtual_size     | 5368709120                                                |
# | visibility       | public                                                    |
# +------------------+-----------------------------------------------------------+

Variáveis de Ambiente

Para interagir com o Openstack podemos especificar parâmetros no próprio cliente ou utilizar variáveis de ambiente, a segunda forma é muito mais comum. Após carregar as variáveis do arquivo openrc podemos listá-las da seguinte forma no nosso ambiente:

env | grep OS_
# output:
# OS_PASSWORD=openstack
# OS_IDENTITY_API_VERSION=3
# OS_TENANT_NAME=demo
# OS_USER_DOMAIN_ID=default
# OS_REGION_NAME=RegionOne
# OS_AUTH_URL=http://10.42.0.15/identity
# OS_USERNAME=demo
# OS_AUTH_TYPE=password
# OS_PROJECT_NAME=demo
# OS_PROJECT_DOMAIN_ID=default
# OS_CACERT=
# OS_VOLUME_API_VERSION=3

Estas variáveis representam nosso usuário, a senha, o ponto de autenticação, o projeto padrão entre outras coisas. No nosso caso, apenas trocar o valor de OS_USERNAME para admin já nos torna administrador do Openstack, por exemplo:

openstack domain list
# output:
# You are not authorized to perform the requested action: identity:list_domains. (HTTP 403) (Request-ID: req-fba7f20a-ca8d-4aca-9a04-20bac073e265)
export OS_USERNAME=admin
openstack domain list
# output:
# <exibição dos domínios>

É uma boa prática criar arquivos de ambiente para cada usuário, ou mesmo para ambientes diferentes.

https://docs.openstack.org/ocata/user-guide/common/cli-set-environment-variables-using-openstack-rc.html

Domínios, projetos, usuários e permissões

O Openstack é uma plataforma que suporta multitenência, isso significa que os usuários podem utilizar sem correr o risco de colidir com os demais utilizadores. Para garantir essa separação lógica, o Openstack é divido entre domínios e projetos.

Podemos separar a hierarquia de segregação do Openstack da seguinte forma:

A region padrão chama-se RegionOne serve apenas para separação lógica de clusters de Openstack.

Domain

O domain contém os projetos. Um domínio padrão chamado Default é criado durante a instalação com usuários locais, recomenda-se que novos usuários sejam criados em outros domínios. Um domínio diferente do Default pode ser conectado a algum gerenciador de identidade (LDAP, SSO) e fornecer uma forma mais robusta de autenticação.

openstack domain create d1

Project

O project contém todos os demais objetos como máquinas virtuais, redes, buckets, discos e etc.

openstack project create --domain d1 p1

User

openstack user create --domain d1 --password openstack operator1

Roles

Por padrão existem apenas duas roles que podemos utilizar no Openstack reconhecida por todos os serviços, member e admin. Adicionando um usuário como admin ele se torna administrador do cluster por completo e não somente do projeto ou domínio específico. A permissão do tipo member permite a criação de redes, imagens, máquinas virtuais e outros serviços que estão dentro apenas dos projetos.

openstack role add --user operator1 --domain d1 admin
# openstack role add --user operator1 --domain d1 member

Para visualizar as permissões:

openstack role assignment list --names

Com o usuário criado podemos baixar seu arquivo de configuração pelo painel do Horizon ou criá-lo manualmente:

bash > operator1-d1.sh <<'EOF'
export OS_REGION_NAME=RegionOne
export OS_INTERFACE=public
export OS_AUTH_URL=http://10.42.0.15/identity
export OS_USERNAME=operator1
export OS_USER_DOMAIN_NAME=d1
export OS_PROJECT_NAME=p1
export OS_PASSWORD=openstack
export OS_IDENTITY_API_VERSION=3
export OS_PROJECT_ID=cda0e77a1b4b421c96075d6d1d77b2e5
export OS_PROJECT_DOMAIN_ID=35e96d353f39439fb944194ff14e08a3
export PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w(operator1-d1)\$ '
EOF

Para carregá-lo, basta executar:

source operator1-d1.sh

Redes

O serviço responsável pela criação e gerenciamento de redes é o Neutron.

Tenant e Provider

As redes tenant são as redes privadas, podem ser criadas pelos usuários comuns e não afetam a rede física. Este tipo de rede possui sua própria máscara, seu servidor DHCP e etc. Já a rede do tipo provider utiliza-se da rede física e dos serviços nela presente, como o próprio DHCP e os ips disponíveis para alocação.

Ao criar uma máquina virtual dentro de uma rede, normalmente a máquina já recebe um endereço IP, a não ser que a rede tenha sido configurada para não ter esse comportamento.

Criar uma rede

Uma rede do Openstack é composta de dois elementos o network e ao menos uma subnet. Para as redes internas, a maioria das informações como DHCP, DNS e CIDR estão na subnet:

openstack network create network1

Criar uma subrede

Toda subnet faz referência a apenas uma network:

openstack subnet create \
--subnet-range 172.27.11.0/24 \
--dns-nameserver 208.67.222.222 \
--dns-nameserver 208.67.220.220 \
--network network1 \
network1-subnet

Acesso a Internet

As redes internas do Openstack são isoladas por padrão. Para conectar as máquinas destas redes na internet precisamos de um roteador conectado a uma rede que consiga se comunicar com o exterior. No caso do DevStack já existe um roteador chamado router1 conectado a rede public para fazer essa funçõa.

openstack router add subnet router1 network1-subnet

Floating IP

Um floating ip é uma forma de expor uma determinada máquina além da rede privada. Com o floating IP podemos acessar os serviços presentes nas máquinas virtuais de forma segura e controlada através de security group.

openstack floating ip create public
openstack floating ip list

Security Group

Um security group atua como um firewall para os servidores e outros serviços em uma rede. Por padrão, todo o tráfego de origem externa para uma máquina é bloqueado, e todo o tráfego de origem interna para fora é permitido. Com security group podemos modificar esse comportamento e bloquear ou abrir portas especificas:

openstack security group rule list \
-c 'IP Protocol' -c Ethertype -c 'IP Range' \
-c 'Port Range' -c Direction -c 'Remote Security Group'
# output:
# +-------------+------+-----------+------------+-----------+-------------------------+
# | IP Protocol | Type | IP Range  | Port Range | Direction | Remote Security Group   |
# +-------------+------+-----------+------------+-----------+-------------------------+
# | None        | IPv6 | ::/0      |            | ingress   | f66d79fd-b36d-476d-b149 |
# | tcp         | IPv4 | 0.0.0.0/0 | 22:22      | ingress   | None                    |
# | icmp        | IPv4 | 0.0.0.0/0 |            | ingress   | None                    |
# | None        | IPv4 | 0.0.0.0/0 |            | ingress   | f66d79fd-b36d-476d-b149 |
# | None        | IPv6 | ::/0      |            | egress    | None                    |
# | None        | IPv4 | 0.0.0.0/0 |            | egress    | None                    |
# +-------------+------+-----------+------------+-----------+-------------------------+

Um exemplo muito comum de regras é a liberação do SSH e do ICMP (ping):

openstack security group rule create --proto icmp --dst-port 0 default
openstack security group rule create --proto tcp --dst-port 22 default

Flavors

Flavors, ou sabores em português, são as configurações pré-definidas de máquinas que podemos escolher durante a criação de um servidor. O DevStack cria alguns flavors para nós:

openstack flavor list
# output:
# +---------------+-------+------+-----------+-------+-----------+
# | Name          |   RAM | Disk | Ephemeral | VCPUs | Is Public |
# +---------------+-------+------+-----------+-------+-----------+
# | m1.tiny       |   512 |    1 |         0 |     1 | True      |
# | m1.small      |  2048 |   20 |         0 |     1 | True      |
# | m1.medium     |  4096 |   40 |         0 |     2 | True      |
# | m1.large      |  8192 |   80 |         0 |     4 | True      |
# | m1.nano       |   128 |    1 |         0 |     1 | True      |
# | m1.xlarge     | 16384 |  160 |         0 |     8 | True      |
# | m1.heat_int   |   512 |   10 |         0 |     1 | True      |
# | m1.micro      |   192 |    1 |         0 |     1 | True      |
# | cirros256     |   256 |    1 |         0 |     1 | True      |
# | ds512M        |   512 |    5 |         0 |     1 | True      |
# | ds1G          |  1024 |   10 |         0 |     1 | True      |
# | ds2G          |  2048 |   10 |         0 |     2 | True      |
# | ds4G          |  4096 |   20 |         0 |     4 | True      |
# | m1.heat_micro |   128 |    1 |         0 |     1 | True      |
# +---------------+-------+------+-----------+-------+-----------+

Além das opções mais óbvias do flavor como CPU, memória e disco, podemos especificar discos efêmeros extras e swap.

openstack flavor create --vcpus 1 --ram 1024 --disk 10 --swap 1 flavor1.small

Neste caso, as máquinas que utilizarem este flavor, terão automaticamente 1GB de memória swap.

Images

O serviço responsável pela criação e gerenciamento de imagens é o Glance.

Podemos utilizar formatos de imagens variados dentro do Openstack, porém os mais comuns são raw e qcow2. O formato raw (cru) é ligeiramente mais performático, aloca todo o espaço necessário de uma vez e não possui nenhum tipo de processamento extra ao gravar os dados. O formato qcow2 (QEMU Copy on Write 2) pode ser alocado dinamicamente, suporta compressão, snapshots e imagens de referência (cow), em compensação possui uma camada extra de processamento em relação ao raw.

openstack image list
# output:
# +--------------------------------------+---------------------------------+--------+
# | ID                                   | Name                            | Status |
# +--------------------------------------+---------------------------------+--------+
# | fa9a7be9-d33e-434a-9bcc-47dee86042ea | Fedora-Cloud-Base-36-1.5.x86_64 | active |
# | d6912fc8-c41d-4b44-aa9f-3101404a537f | cirros-0.5.2-x86_64-disk        | active |
# +--------------------------------------+---------------------------------+--------+

Podemos encontrar imagens especificamente prontas para o Openstack em https://docs.openstack.org/image-guide/obtain-images.html ou mesmo baixar imagens genéricas para cloud.

Por exemplo, para adicionar uma imagem do Debian em nosso ambiente, devemos primeiro baixá-la:

DEBIAN_IMAGE=https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-generic-amd64.qcow2
wget $DEBIAN_IMAGE -O debian-11-amd64.qcow2

Podemos verificar mais informações sobre a imagem com o comando qemu-img:

qemu-img info debian-11-amd64.qcow2 
# output:
# image: debian-11-amd64.qcow2
# file format: qcow2
# virtual size: 2 GiB (2147483648 bytes)
# disk size: 318 MiB
# cluster_size: 65536
# Format specific information:
#     compat: 1.1
#     compression type: zlib
#     lazy refcounts: false
#     refcount bits: 16
#     corrupt: false
#     extended l2: false

Note que o tamanho atual da imagem é 318 MiB, mas seu tamanho expendido é 2 GiB. A imagem está compactada.

Para subir a imagem para dentro do Openstack, executamos o seguinte comando:

openstack image create --file debian-11-amd64.qcow2 --disk-format qcow2 debian-11-amd64

Customização

As vezes faz sentido ter uma imagem com todas as definições pré-definidas. Isso ajuda na inicialização, já que a máquina não precisará se configurar. Essa velocidade e bastante útil nos casos em que as aplicações precisam escalar, e por isso é a forma padrão utilizada em contêineres.

Existem muitos comandos para customizar imagens no Linux, um deles é o virt-customize presente no pacote guestfs-tools. Podemos modificar a senha do root, instalar pacotes e outras coisas sem a necessidade de uma máquina virtual:

sudo virt-customize -a debian.qcow2 \
--root-password password:debian \
--install apache2,libapache2-mod-php

Máquinas Virtuais

O serviço responsável pela criação e gerenciamento de máquinas virtuais é o Nova.

Para criar uma máquina virtual devemos definir ao menos quatro parâmetros:

Um exemplo mais completo para a criação de máquina virtual seria o seguinte:

openstack server create \
--image cirros-0.5.2-x86_64-disk \
--network shablau \
--flavor m1.nano \
--user-data cloud-init.yml \
--key-name key \
cirros

User Data

O user data são informações que podemos entregar para a máquina virtual executar algo durante a sua primeira inicialização. Podemos fornecer os mais variados tipos de scripts desde que a máquina virtual possa interpretá-los, exemplos muito comuns são shell script e cloud-init.

Scripts

Os scripts do user data podem ser escritos em qualquer linguagem de programação, desde que a máquina possa interpretá-lo. Exemplo interessantes são shell script, perl, python, lua, etc.

#!/bin/bash

echo fedora | passwd fedora --stdin

Cloud-init

O cloud-init contém instruções quase sempre descritivas sobre como deve ser o estado da máquina. Seu formato é chamado de YAML. Arquivos YAML possuem sintaxe própria e podem representar estruturas de dados assim como JSON.

#cloud-config
password: openstack
chpasswd: {expire: False}

package_update: true
packages:
 - apache2
 - mariadb-server
   
runcmd:
- systemctl enable --now mysql apache2

Volumes

O serviço responsável pela criação e gerenciamento de volumes é o Cinder.

Quando criamos uma máquina no Openstack sem nenhum parâmetro específico de disco a máquina recebe um disco efêmero. Os discos efêmeros são destruidos juntamente com a máquina virtual.

Podemos criar um volume com o seguinte comando:

openstack volume create --size 10 mysql

Também é possível criar um volume baseado em uma imagem, e assim ter um disco de boot maior que o tamanho já definido. No exemplo abaixo criamos um volume baseado na imagem do cirros, mas aumentamos o volume para 5GB:

openstack volume create --image cirros-0.5.2-x86_64-disk --size 5 cirros-persistent

Para listar os volumes utilizamos openstack volume list, o campo Attached to quando preenchido, mostra em qual máquina o disco está, isso modifica o campo Status para in-use ao invés de available.

openstack volume list
# output:
# +-------------------------+--------+-----------+------+---------------------------------+
# | ID                      | Name   | Status    | Size | Attached to                     |
# +-------------------------+--------+-----------+------+---------------------------------+
# | 29f9f70f-125e-441f-920d | cirros | available |    5 |                                 |
# | b68b9e2e-25fe-49f3-96d9 | mysql  | in-use    |   10 | Attached to cirros on /dev/vdb  |
# +-------------------------+--------+-----------+------+---------------------------------+

Podemos adiconar um volume em uma máquina virtual criada com o seguinte comando:

openstack server add volume cirros mysql
# output:
# +-----------------------+-------------------------+
# | Field                 | Value                   |
# +-----------------------+-------------------------+
# | ID                    | b68b9e2e-25fe-49f3-96d9 |
# | Server ID             | 3c92963b-3e5c-4760-8076 |
# | Volume ID             | b68b9e2e-25fe-49f3-96d9 |
# | Device                | /dev/vdb                |
# | Delete On Termination | False                   |
# +-----------------------+-------------------------+

Para criar uma máquina vitual com um disco persistente, basta referenciá-lo durante a criação ao invés de uma imagem:

openstack server create --volume cirros --flavor m1.micro --network private cirros

Object Storage

Por padrão o Ceph utiliza o Swift como object storage (também conhecido como “bucket”), capaz de armazenar petabytes de dados de forma escalável e redundante. O swift possui seu próprio protocolo mas também é relativamente compatível com S3.

O swift chama o local de armazenamento dos arquivos de “container” apesar do nome “bucket” ser mais comum, tratando-se deste storage estes termos são intercambiáveis.

openstack container create configurations

cat > ports.conf <<EOF
# If you just change the port or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf

Listen 80

<IfModule ssl_module>
        Listen 443
</IfModule>

<IfModule mod_gnutls.c>
        Listen 443
</IfModule>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
EOF

openstack object create configurations ports.conf
openstack object list configurations
cd /tmp
openstack object save configurations ports.conf 
cat ports.conf

Utilizando o curl

Utilizar o curl para fazer consultas e baixar arquivos do Swift é relativamente simples e facilita bastante a utilização de arquivos de configuração externos às máquinas. Além da consulta podemos executar todas as operações que normalmente faríamos através do cliente openstack, como enviar e deletar arquivos.

Para isso precisaremos:

openstack token issue
# output:
# +------------+-----------------------------------------------------------------+
# | Field      | Value                                                           |
# +------------+-----------------------------------------------------------------+
# | expires    | 2023-02-26T04:11:31+0000                                        |
# | id         | gAAAAABj-                                                       |
# |            | s3jQorDC4WTsxCu6Pr375PGy5RNRwZPmTVZyzzCvtL5mWImRbXmkkl6Hs-dCgq0 |
# |            | vEdczLdAw1Amjie903PgLPEW_YZ0c-                                  |
# |            | 8kiNLbbQbRgsm6j2Au06Vve6sSxddYLFwCoqSt1liIIe3bFTocUfBksazyafge2 |
# |            | UYVbtdeMoOdXuFdRVU                                              |
# | project_id | a20a6ced757e4c0f9b7048154654d9fe                                |
# | user_id    | 80b554bf14b24e25806094b3810f4ec8                                |
# +------------+-----------------------------------------------------------------+

Copiar o token e salvar em uma variável para melhor conveniência, o token não possui quebras de linhas.

export TOKEN='gAAAAABj...2OnQ77POMschrNk'

Obter a conta do bucket:

openstack container show configurations
# output:
# +----------------+---------------------------------------+
# | Field          | Value                                 |
# +----------------+---------------------------------------+
# | account        | AUTH_a20a6ced757e4c0f9b7048154654d9fe |
# | bytes_used     | 334                                   |
# | container      | configurations                        |
# | object_count   | 1                                     |
# | storage_policy | Policy-0                              |
# +----------------+---------------------------------------+

Normalmente o swift possui um endereço como swift.openstack.example.com o que torna a utilização muito mais fácil, mas em alguns para acessar o swift necessitaremos de um endereço IP e uma porta, como é o caso do DevStack. Para descobrir onde o swift está, basta verificarmos os endpoints do Openstack:

openstack endpoint list -c 'Service Name' -c 'Service Type' -c URL
# output:
# +--------------+----------------+-----------------------------------------------+
# | Service Name | Service Type   | URL                                           |
# +--------------+----------------+-----------------------------------------------+
# | glance       | image          | http://10.42.0.15/image                       |
# | placement    | placement      | http://10.42.0.15/placement                   |
# | nova_legacy  | compute_legacy | http://10.42.0.15/compute/v2/$(project_id)s   |
# | cinder       | block-storage  | http://10.42.0.15/volume/v3/$(project_id)s    |
# | swift        | object-store   | http://10.42.0.15:8080/v1/AUTH_$(project_id)s |
# | heat-cfn     | cloudformation | http://10.42.0.15/heat-api-cfn/v1             |
# | nova         | compute        | http://10.42.0.15/compute/v2.1                |
# | heat         | orchestration  | http://10.42.0.15/heat-api/v1/$(project_id)s  |
# | cinderv3     | volumev3       | http://10.42.0.15/volume/v3/$(project_id)s    |
# | neutron      | network        | http://10.42.0.15:9696/networking             |
# | swift        | object-store   | http://10.42.0.15:8080                        |
# | keystone     | identity       | http://10.42.0.15/identity                    |
# +--------------+----------------+-----------------------------------------------+

Um exemplo de listagem e download de arquivo através do curl:

curl -H "X-Auth-Token: $TOKEN" http://10.42.0.15:8080/v1/AUTH_a20a6ced757e4c0f9b7048154654d9fe
# configurations
curl -H "X-Auth-Token: $TOKEN" http://10.42.0.15:8080/v1/AUTH_a20a6ced757e4c0f9b7048154654d9fe/configurations
# ports.conf

Heat

O Heat é o serviço de orquestração do Openstack que funciona de forma declarativa. Através do Heat podemos definir templates de nossas aplicações, conhecido como Heat Orchestration Template ou HOT.

Uma stack é considerada como uma unidade, podemos consultar o estado de cada objeto separadamente e atualizá-los de forma isolada, sem impactar os outros elementos.

Abaixo temos um exemplo de um exemplo de uma stack que instala um server com apache, faz algumas customizações na inicialização e configura um security group:

heat_template_version: wallaby
description: Servidor Apache

parameters:
  image:
    type: string
    description: imagem do webserver
    default: debian-11-amd64 
  key:
    type: string
    description: chave ssh para acessar o webserver
  text:
    type: string
    description: texto para aparecer no servidor
    default: '<h1>IaaS</h1>'

resources:

  apache_security_group:
    type: OS::Neutron::SecurityGroup
    properties:
      rules:
      - { direction: ingress, protocol: icmp }
      - { direction: ingress, protocol: tcp, port_range_min: 22, port_range_max: 22 }
      - { direction: ingress, protocol: tcp, port_range_min: 80, port_range_max: 80 }
      - { direction: ingress, protocol: tcp, port_range_min: 443, port_range_max: 443 }

  apache:
    type: OS::Nova::Server
    properties:
      flavor: m1.micro
      networks:
      - network: private
      image: { get_param: image }
      key_name: { get_param: key } 
      security_groups:
      - get_resource: apache_security_group
      user_data:
        str_replace:
          template: | 
            #!/bin/bash
            apt-get update && apt-get install -y apache2
            echo '$text' > /var/www/html/index.html
          params:
            $text: { get_param: text }

Para aplicar a stack precisamos acessar o arquivo de alguma forma, o arquivo pode ser local ou estar em algum endereço:

openstack stack create --template apache.yml apache --parameter key=mit

Assim que criarmos a stack, podemos consultar o estado de cada elemento da stack:

openstack stack resource list apache
# output:
# +-----------------------+-------------------------+----------------------------+-----------------+----------------------+
# | resource_name         | physical_resource_id    | resource_type              | resource_status | updated_time         |
# +-----------------------+-------------------------+----------------------------+-----------------+----------------------+
# | apache                | 4e7c3cd7-ada4-475c-9c45 | OS::Nova::Server           | CREATE_COMPLETE | 2023-03-12T14:44:17Z |
# | apache_security_group | 36ecafff-b653-401f-af49 | OS::Neutron::SecurityGroup | CREATE_COMPLETE | 2023-03-12T14:44:17Z |
# +-----------------------+-------------------------+----------------------------+-----------------+----------------------+

Uma vez que a stack esteja completa, podemos simplesmente atualizá-la:

openstack stack update \
--template apache.yml apache \
--parameter key=mit \
--parameter text='<h1>Openstack!</h1>'

Se listarmos os recursos, veremos que apenas o servidor foi atualizado:

openstack stack resource list apache
# output:
# +-----------------------+-------------------------+----------------------------+-----------------+----------------------+
# | resource_name         | physical_resource_id    | resource_type              | resource_status | updated_time         |
# +-----------------------+-------------------------+----------------------------+-----------------+----------------------+
# | apache_security_group | 570707af-c3c2-4a91-959a | OS::Neutron::SecurityGroup | CREATE_COMPLETE | 2023-03-12T14:46:01Z |
# | apache                | a3c34634-b7b6-423b-885e | OS::Nova::Server           | CREATE_COMPLETE | 2023-03-12T14:49:13Z |
# +-----------------------+-------------------------+----------------------------+-----------------+----------------------+