27 KiB
Zadania do realizacji w czasie zajęć
W trakcie tych zajęć zautomatyzujemy instalację Wordpresa. Celem jest osiągnięcie tej samej konfiguracji co poprzednio, jednak tak aby wszystko zostało zainstalowane i uruchomione przy wykonaniu jednego skryptu.
Instalacja CLI
Pobierz najnowsze wydanie _hcloud CLI: https://github.com/hetznercloud/cli/releases/latest/download/hcloud-linux-amd64.tar.gz. Pobrany plik rozpakuj
tar -xvzf hcloud-linux-amd64.tar.gz
Zweryfikuj działanie CLI:
./hcloud version
Wskazówka: Pamiętaj aby plik rozpakować w miejscu, w którym możliwe jest uruchamianie plików wykonywalnych.
Dla łatwiejszego wpisywania poleceń w zalecane jest skonfigurowanie odpowiedniego autouzupełniania składni:
- bash:
source <(./hcloud completion bash)
- fish:
./hcloud completion fish | source
- bash:
Przed użyciem konieczne jest udzielenie uprawnień naszemu narzędziu konsolowemu do naszego konta. Jedyną dostępną opcją jest wykorzystanie _tokenu autoryzacyjnego.
./hcloud context create pzc
Polecenie to zażąda od nas podania tokenu dostępowego. W tym celu należy wygenerować go korzystając z webowego interfejsu (projekt PZC -> zabezpieczenia -> tokeny API). Należy wybrać uprawnienia do odczytu i zapisu, bez tego nie będzie można tworzyć nowych zasobów.
Uwaga: Należy pilnować bezpieczeństwa tokenu, co prawda nie daje on pełnych uprawnień do konta jak login i hasło, lecz wciaż umożliwia tworzenie dowolnych zasobów.
Uwaga: Token zostanie zapisany w pliku
~/.config/hcloud/cli.toml
. Tam też trafiają pozostałe opcje konfiguracyjne.Utworzony kontekst zostanie automatycznie aktywowany, od tego momentu wszystkie polecenia będą wykonywane w jego obrębie.
Spróbujmy utworzyć nową maszynę wirtualną z wiersza poleceń.
./hcloud server create --image ubuntu-20.04 --type cx11 --name pzc-test-1 --location hel1
Listę dostępnych lokalizacji, obrazów systemu, i typów instancji możesz uzyskać dzięki następującym poleceniom:
./hcloud locations list ./hcloud image lsit ./hcloud server-type list
Przykładowo proces utworzenia maszyny wirtualnej może wyglądać następująco
$ ./hcloud server create --image ubuntu-20.04 --type cx11 --name pzc-test-1 --location hel1 4.551s [=================================] 100.00% Waiting for server 14328689 to have started ... done Server 14328689 created IPv4: 95.216.211.233 Root password: 7eN7CihrEvbuViciCmsq
Zweryfikuj w przeglądarce czy maszyna wirtualna została rzeczywiście utworzona.
Dokladne informacje o istniejącej maszynie można uzyskać dzięki poleceniu:
./hcloud server describe pzc-test-1
Gdy maszyna nie jest już potrzebna można ją usunać:
./hcloud server delete pzc-test-1
Wskazówka: W zadaniach domowych możesz założyć, że wszystkie te kroki zostały wykonane w katalogu
/dev/shm
oraz, ze aktywny jest odpowiedni kontekst.
Biblioteka Python
CLI jest bardzo wygodnym klasycznym rozwiązaniem, jednak nie zawsze jest wystarczająco elastyczne. Możliwy jest dostęp do zasobów chmurowych z poziomu języka programowania, większość dostawców dostarcza biblioteki dla najpopuparniejszych jezyków programowania.
Dla chmury Hetzner dostępna jest przykładowo biblioteka dostępowa w języku Python: https://github.com/hetznercloud/hcloud-python). Analogicznie jak w przypadku CLI, konieczne jest posiadanie tokenu dostępowego (może być ten sam co poprzednio). Poniższy fragment kodu tworzy instancję interfejsu dostępowego do chmury:
from hcloud import Client
client = Client(
token="ftGv4HLU2FsZCD9JEfN8suRh1X4rHImwCOJHP4eKsntvxGyjHR39ADgzYVrbw7m5"
)
Analogicznie jak w CLI możemy utworzyć nową maszynę wirtualną:
from hcloud.images.domain import Image
from hcloud.server_types.domain import ServerType
response = client.servers.create(
name="pzc-test", server_type=ServerType("cx11"), image=Image(name="ubuntu-20.04")
)
print(f"Utworzono serwer: {response.server.data_model.name} ({response.server.data_model.public_net.ipv4.ip})")
Utworzono serwer: pzc-test (95.217.183.63)
Po zakończeniu pracy można usunąć wszystkie stworzone serwery korzystająć z prostego skryptu:
servers = client.servers.get_all()
print(f"Usuwanie {len(servers)} serwerów")
for s in servers:
action = client.servers.delete(s)
print(f"Usuwanie serwera {s.data_model.name} ({s.data_model.public_net.ipv4.ip}): {action.data_model.status}")
Usuwanie 1 serwerów Usuwanie serwera pzc-test (95.217.183.63): success
Automatyzacja instalacji Wordpress
Przejdziemy teraz do realizacji głównego zadania przewidzianego na dzisiejsze zajęcia. Analogicznie jak na poprzednich zajęciach chcemy uzyskać instalację Wordpresa wraz z konfigurowanym równoważeniem obciążenia, zgodnie z poniższym schematem:
Tworzenie instalacji Wordpress rozpoczynamy od dodania klucza SSH do naszego konta.
ssh_key = client.ssh_keys.create(name="pzc-ssh-key", public_key="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINXcCzwPDRJm+ICREkvIXRg6A0HEmasC5fLxdqK6P5zm bikol@spire")
print(f"Klucz {ssh_key.data_model.name} został dodany: {ssh_key.data_model.public_key}")
Klucz pzc-ssh-key został dodany: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINXcCzwPDRJm+ICREkvIXRg6A0HEmasC5fLxdqK6P5zm bikol@spire
Teraz przystępujemy do tworzenia właściwych zasobów. Zacznijmy od wirtualnej sieci:
from hcloud.networks.domain import NetworkSubnet
vnet = client.networks.create(
name="pzc-test-vnet",
ip_range="10.10.10.0/24",
subnets=[
NetworkSubnet(ip_range="10.10.10.0/24", network_zone="eu-central", type="cloud")
]
)
print(f"Utworzono sieć wirtualną {vnet.data_model.name} ({vnet.data_model.ip_range})")
Utworzono sieć wirtualną pzc-test-vnet (10.10.10.0/24)
Kolejnym etapem jest utworzenie maszyn wirtualnych. Skorzystamy z narzędzia cloud-init aby móc automatycznie nie tylko stworzyć maszynę, ale również zainstalować i uruchomić na niej porządane oprogramowanie. Konfiguracja z wykorzystaniem cloud-init
pozwala zdefiniować jakie co ma być wykonane po utworzeniu maszyny wirtualnej. Ważne jest to, że wszystko zostanie wykonane automatycznie bez potrzeby logowania się przez SSH czy innej interakcji z serwerem. cloud-init
pozwala na wykonanie dowolnych operacji na serwerze. Służy do tego sekcja runcmd
, w której definiujemy polecenia jakie mają zostać wykonane w konsoli. Dla uproszczenia najczęściej wykonywane zadania mają przewidzianą osobną składnie. Przykładowo można
- tworzyć nowych użytkowników i grupy
- dodawać i modyfikować pliki
- instalować nowe oprogramowania i dodawać nowe repozytoria
- zarządzać zaufanymi certyfikatami
- zarządzać zamontowanymi zasobami dyskowymi
- zarządzać kluczami SSH
Na potrzeby tego zadania wykorzystamy tylko część z dostępnych możliwości. Ponadto wykorzystamy tylko jeden z wielu możliwych formatów konfiguracji cloud-init
o nazwie cloud config
.
cloud_init_db=r'''#cloud-config
# lista podstawowych pakietów, które należy zainstalować
packages:
- apt-transport-https
- ca-certificates
- curl
- gnupg-agent
- software-properties-common
# tworzymy plik docker-compose.yml
write_files:
- path: /root/docker-compose.yml
content: |
version: '3.9'
services:
db:
image: mysql:5.7
restart: always
ports:
- "10.10.10.2:3306:3306"
environment:
MYSQL_ROOT_PASSWORD: notSecureChangeMe
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
volumes:
- db_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin
restart: always
# przekierowanie portów zostawione tylko dla przykładu, nie należy tak robić na produkcji
ports:
- "8080:80"
volumes:
db_data: {}
# instalujemy docker, docker-compose a następnie uruchamiamy naszą bazę danych
runcmd:
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
- add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
- apt-get update -y
- apt-get install -y docker-ce docker-ce-cli containerd.io
- curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
- chmod +x /usr/local/bin/docker-compose
- systemctl start docker
- systemctl enable docker
- cd /root/ && docker-compose up -d
'''
from hcloud.locations.domain import Location
db_server = client.servers.create(
name="db",
server_type=ServerType("cx11"),
image=Image(name="ubuntu-20.04"),
ssh_keys=[ssh_key],
networks=[vnet],
location=Location("hel1"),
user_data=cloud_init_db
)
db_server.action.wait_until_finished()
print(f"Tworzenie serwera db: {db_server.action.complete}")
Tworzenie serwera db: True
Serwer został utworzony, możemy pobrać z systemu jego aktualne dane (w tym adres IP).
Uwaga W skrypcie
cloud-init
przyjeliśmy, że maszyna będzie miała przydzielony adres IP10.10.10.2
, jeśli tak się nie stało to w dalszych krokach Wordpress może mieć problem z kominukacją z bazą danych.
db_server = client.servers.get_by_name("db")
print(f"Serwer: {db_server.data_model.name}\n\tpubliczne IP: {db_server.data_model.public_net.ipv4.ip}\n\tprywatne IP: {db_server.data_model.private_net[0].ip}")
Serwer: db publiczne IP: 135.181.201.125 prywatne IP: 10.10.10.2
Na tym etapie możesz zweryfikować, że wszystko działa jak należy. Odwiedź w przeglądarce http://<publiczny adres IP>:8080
.
Uwaga: Utworzenie maszyny wirtualnej trwa zaledwie kilka sekund, jednak wykonanie całego skryptu
cloud-init
może potrwać nawet kilka minut. Jeśli serwer nadal nie odpowiada pod wskazanym adresem, poczekaj kilka minut.
Wskazówka: Jeśli coś się nie powiodło i chcesz sprawdzić co poszło nie tak, zawsze możesz zalogować się na maszynę wirtualną korzystając z SSH. Warto wtedy sprawdzić zawartość pliku
/var/log/cloud-init.log
.
Przystępujemy do kolejnego kroku czyli utworzenia pierwszej instancji Wordpress. Skrypt cloud-init
różni się tylko treścią pliku docker-compose.yml
, pozostała część skryptu pozostaje bez zmian.
cloud_init_wp1=r'''#cloud-config
# lista podstawowych pakietów, które należy zainstalować
packages:
- apt-transport-https
- ca-certificates
- curl
- gnupg-agent
- software-properties-common
# tworzymy plik docker-compose.yml
write_files:
- path: /root/docker-compose.yml
content: |
version: '3.9'
services:
wordpress:
image: wordpress:latest
volumes:
- wordpress_data:/var/www/html
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: "10.10.10.2:3306"
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
wordpress_data: {}
# instalujemy docker, docker-compose a następnie uruchamiamy naszą bazę danych
runcmd:
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
- add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
- apt-get update -y
- apt-get install -y docker-ce docker-ce-cli containerd.io
- curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
- chmod +x /usr/local/bin/docker-compose
- systemctl start docker
- systemctl enable docker
- cd /root/ && docker-compose up -d
'''
wp1_server = client.servers.create(
name="wordpress-1",
server_type=ServerType("cx11"),
image=Image(name="ubuntu-20.04"),
ssh_keys=[ssh_key],
networks=[vnet],
location=Location("hel1"),
user_data=cloud_init_wp1
)
wp1_server.action.wait_until_finished()
print(f"Tworzenie serwera wordpress-1: {wp1_server.action.complete}")
Tworzenie serwera wordpress-1: True
wp1_server = client.servers.get_by_name("wordpress-1")
print(f"Serwer: {wp1_server.data_model.name}\n\tpubliczne IP: {wp1_server.data_model.public_net.ipv4.ip}\n\tprywatne IP: {wp1_server.data_model.private_net[0].ip}")
Serwer: wordpress-1 publiczne IP: 65.108.62.231 prywatne IP: 10.10.10.3
Przejdź teraz w przeglądarce pod http://<publiczny adres IP serwera wordpress-1>:8000
i dokończ instalację Wordpress.
Uwaga: Z pewnością możliwe jest zautoamtyzowanie tego kroku, jednak wykracza to poza zakres tych zajęć.
Teraz musimy utworzyć migawkę tej maszyny wirtualnej a następnie użyć jej do utworzenia nowej maszyny wirtualnej.
response = wp1_server.power_off()
response.wait_until_finished()
print("Serwer wordpress-1 zatrzymany")
response = client.servers.create_image(
server=wp1_server
)
response.action.wait_until_finished()
wp1_image = response.image
print(f"Utworzono migawkę serwera wordpress-1: {response.action.complete}")
response = wp1_server.power_on()
response.wait_until_finished()
print("Serwer wordpress-1 uruchomiony")
Serwer wordpress-1 zatrzymany Utworzono migawkę serwera wordpress-1: True Serwer wordpress-1 uruchomiony
wp2_server = client.servers.create(
name="wordpress-2",
server_type=ServerType("cx11"),
image=wp1_image, # tutaj wykorzystujemy utworzoną wcześniej migawkę
ssh_keys=[ssh_key],
networks=[vnet],
location=Location("hel1") # tym razem nie ma potrzeby podawać skryptu cloud-init
)
wp2_server.action.wait_until_finished()
print(f"Tworzenie serwera wordpress-2: {wp2_server.action.complete}")
Tworzenie serwera wordpress-2: True
wp2_server = client.servers.get_by_name("wordpress-2")
print(f"Serwer: {wp2_server.data_model.name}\n\tpubliczne IP: {wp2_server.data_model.public_net.ipv4.ip}\n\tprywatne IP: {wp2_server.data_model.private_net[0].ip}")
Serwer: wordpress-2 publiczne IP: 95.217.167.221 prywatne IP: 10.10.10.4
Ostatnim krokiem jest konfiguracja mechanizmu równoważenia obciążenia.
from hcloud.load_balancer_types.domain import LoadBalancerType
from hcloud.load_balancers.domain import LoadBalancerAlgorithm, LoadBalancerServiceHttp, LoadBalancerHealthCheck, LoadBalancerService, LoadBalancerHealtCheckHttp, LoadBalancerTarget
lb = client.load_balancers.create(
name="lb-wp",
load_balancer_type=LoadBalancerType(name="lb11"),
location=Location("hel1"),
services=[
LoadBalancerService(
protocol="http",
listen_port=8000,
destination_port=8000,
proxyprotocol=False,
health_check=LoadBalancerHealthCheck(
protocol="http",
port="8000",
interval=15,
timeout=10,
retries=3,
http=LoadBalancerHealtCheckHttp(
path="/",
status_codes=["2??", "3??"],
tls=False
)
),
http=LoadBalancerServiceHttp(
sticky_sessions=True,
cookie_name="HCLBSTICKY",
cookie_lifetime=300,
certificates=[]
)
)
],
targets=[
LoadBalancerTarget(
type="server",
server=wp1_server,
use_private_ip=True
),
LoadBalancerTarget(
type="server",
server=wp2_server,
use_private_ip=True
)
],
public_interface=True,
network=vnet
)
lb.action.wait_until_finished()
print(f"Mechanizm równoważenia obciążenia został utworzony: {lb.action.complete}")
Mechanizm równoważenia obciążenia został utworzony: True
lb = client.load_balancers.get_by_name("lb-wp")
print(f"Publiczny adres IP: {lb.data_model.public_net.ipv4.ip}")
Publiczny adres IP: 95.217.169.62
Zweryfikuj instalację w przeglądarce pod adresem: http://<publiczny adres IP LB>:8000
.
Udało się uzyskać konfigurację analogiczną jak poprzednio, tym razem jednak całość została wykonana za pomocą skryptu w języku Python. Pokazuje to jak bardzo elastyczne może być pracowanie w chmurze w modelu IaaS.
Po zakończeniu pracy rekomenduje usunięcie wszystkich utworzonych zasobów.
lbs = client.load_balancers.get_all()
print(f"Usuwanie {len(lbs)} mechanizmów LB")
for s in lbs:
action = client.load_balancers.delete(s)
print(f"\tUsuwanie LB {s.data_model.name}: {action}")
images = client.images.get_all(type="snapshot")
print(f"Usuwanie {len(images)} migawek")
for s in images:
action = client.images.delete(s)
print(f"\tUsuwanie migawki {s.data_model.name}: {action}")
servers = client.servers.get_all()
print(f"Usuwanie {len(servers)} serwerów")
for s in servers:
action = client.servers.delete(s)
print(f"\tUsuwanie serwera {s.data_model.name} ({s.data_model.public_net.ipv4.ip}): {action.data_model.status}")
ssh_keys = client.ssh_keys.get_all()
print(f"Usuwanie {len(ssh_keys)} kluczy SSH")
for s in ssh_keys:
action = client.ssh_keys.delete(s)
print(f"\tUsuwanie klucza {s.name}: {action}")
vnets = client.networks.get_all()
print(f"Usuwanie {len(vnets)} sieci wirtualnych")
for s in vnets:
action = client.networks.delete(s)
print(f"\tUsuwanie sieci wirtualnej {s.name}: {action}")
volumes = client.volumes.get_all()
print(f"Usuwanie {len(vnets)} wolumenów")
for v in volumes:
action = client.volumes.delete(v)
print(f"\tUsuwanie wolumenu {v.name}: {action}")
Usuwanie 0 mechanizmów LB Usuwanie 0 migawek Usuwanie 0 serwerów Usuwanie 0 kluczy SSH Usuwanie 0 sieci wirtualnych Usuwanie 0 wolumenów Usuwanie wolumenu pb-volume: True Usuwanie wolumenu s470627: True Usuwanie wolumenu s434804-volume: True Usuwanie wolumenu s434704-gitea-volume: True