Pulumi - Infrastruktura jako kod [ Digital Ocean ]
Za pomocą Pulumi możesz zdefiniować swoją infrastrukturę IT w pliku opisanym za pomocą twojego ulubionego języka programowania. Ten artykuł pokazuje, jak to zrobić.
Daniel Gustaw
• 10 min read
Jak zmieniało się wdrażanie w ciągu lat?
Początkowo (70 lat temu) komputery były programowane poprzez ręczną zmianę połączeń kablowych. Nie było osobistego komputera stacjonarnego, więc programy były pisane na produkcji. Brak wdrożenia - brak problemu.
pierwsi programiści komputerów cyfrowych
Około dekadę później IBM wprowadził karty dziurkowane, więc wdrożenie lub instalacja odbywała się poprzez wsunięcie karty z programem do czytnika.
Kolejny przełom - internet i protokoły ftp (1971) zmieniły wszystko. Aby przenieść programy na inne maszyny, przestano potrzebować fizycznego nośnika oprócz kabli łączących z siecią.
Generalnie proces był prosty. Należało skopiować pliki binarne na mainframe i uruchomić tutaj program.
Istniał tylko jeden problem. Koszt komputerów był ogromny, więc aby je optymalnie wykorzystać i odzyskać inwestycję, wprowadzono wirtualizację - logiczne dzielenie zasobów maszyny pomiędzy procesy.
Niestety na maszynach x86 występował problem z wirtualizacją niektórych instrukcji procesora, a w latach 1980-1990 ta metoda została porzucona na koniec epoki superkomputerów.
Wracamy do wirtualizacji w 1990 roku, nie tylko z powodu rosnących kosztów i nieefektywności utrzymania infrastruktury, ale także dzięki technice wirtualizacji adaptacyjnej wprowadzonej przez VMware. Wykryli instrukcje, które były problematyczne podczas wirtualizacji i przekształcili je na bezpieczne odpowiedniki.
Moim zdaniem, dla procesu wdrażania to rewolucja porównywalna do internetu. Internet zcyfryzował nośnik programu podczas wdrażania, ale wirtualizacja pozwoliła na zmianę parametrów serwera bez dotykania sprzętu.
Te zmiany były ważne dla dostawców chmury i branży IaaS, ale deweloperzy byli podzieleni na tych, którzy umieszczali na serwerach swoje własne maszyny wirtualne, oraz tych, którzy używali ftp lub scp do kopiowania programów na VPS-y dostarczane przez ich dostawców.
Kolejną ogromną rewolucją był Docker, napisany w Go w 2013 roku, i wykorzystujący przestrzenie nazw dostępne od jądra Linux 2.6.24 z 2008 roku. Jego popularność eksplodowała w 2017 roku. Głównym problemem czasów przed Dockerem była konfiguracja serwera do używania naszej aplikacji. Kiedy środowisko uruchomieniowe było skonfigurowane na serwerze, wtedy dwie różne aplikacje z różnymi wersjami lub konfiguracją uruchomieniową nie mogły być zainstalowane na tym samym hoście. Jeśli były pakowane jako maszyny wirtualne, cała warstwa systemu operacyjnego była duplikowana. Docker rozwiązał te problemy.
Ale to nie koniec. Gdy ludzie nauczyli się korzystać z Dockera i stworzyli przepływy CI/CD do automatyzacji procesów wdrażania i testowania, zdali sobie sprawę, że zarządzanie serwerami można również realizować na poziomie kodu.
Infrastruktura jako kod ma tę samą przewagę nad panelem administracyjnym w zarządzaniu serwerami, co interfejs wiersza poleceń nad GUI w zarządzaniu osobistym komputerem stacjonarnym - jest uniwersalna i można nią łatwo zarządzać za pomocą skryptów napisanych przez użytkownika końcowego.
Dominującą pozycję wśród produktów IaC ma Terraform rozwijany przez Hashicorp. Jest łatwy do nauczenia, ale wymaga własnego języka konfiguracyjnego.
to jest przykładowa konfiguracja:
provider "aws" {
region = "us-west-2"
}
provider "random" {}
resource "random_pet" "name" {}
resource "aws_instance" "web" {
ami = "ami-a0cfeed8"
instance_type = "t2.micro"
user_data = file("init-script.sh")
tags = {
Name = random_pet.name.id
}
}
Nie będziemy się skupiać na tej części kodu. Jeśli jesteś tym zainteresowany i znasz język polski, napisałem artykuł na temat terraform tutaj:
Infrastrukura defniowana przez kod (terraform + digital ocean)
Teraz skupimy się na konkurencie Terraform. Porozmawiajmy o pulumi.
Pulumi
Pulumi to platforma do definiowania infrastruktury za pomocą programów napisanych w:
- typescript
- python
- go
- c#
- java
- yaml
Obsługuje ponad 70 dostawców chmur. Kluczową różnicą w porównaniu do terraformu jest prostota dostosowywania uzyskiwana dzięki przetwarzaniu danych używanych do definiowania parametrów infrastruktury.
Zamiast pliku z init-script
, który należy wykonać na nowym vps
, mogę użyć szablonu i zrenderować go z moimi zmiennymi środowiskowymi. Zamiast kopiować konfigurację, mogę przechodzić przez środowiska, łącząc subdomeny z odpowiednimi serwerami.
Aby zainstalować pulumi cli, uruchom polecenie:
curl -sSL https://get.pulumi.com | sh
Możemy zacząć od utworzenia katalogu dla naszej konfiguracji:
mkdir pulumi && cd pulumi
Teraz chcemy stworzyć nowy projekt korzystając z szablonu. Aby wyświetlić szablony typescript, możemy wpisać:
pulumi new -l | grep typescript
Stwórzmy konfigurację dla Digital Ocean
pulumi new digitalocean-typescript
Teraz zainstalujemy dwa pakiety:
- dotenv - aby używać wartości zdefiniowanych w pliku .env i dodać je do
process
- jsrender - aby renderować szablon skryptu do przygotowania serwera
npm i dotenv jsrender
Teraz możemy utworzyć plik .env
z dwiema wartościami:
DOCKER_REGISTRY_DOMAIN=___
DOCKER_TOKEN=__
Musimy przygotować wszystkie serwery w ten sam sposób. Poniżej przedstawiam nasze wymagania:
- musimy być zalogowani do rejestru docker
- tmux musi być skonfigurowany z odpowiednim przypisaniem klawiszy
- snap musi być wyłączony i usunięty, aby uniknąć błędu 100% CPU
- python z zainstalowanymi pakietami
docker
idocker-compose
dla ansible - zainstalowane i uruchomione
nginx-proxy-automation
Aby to zrealizować, stwórzmy userData.sh
z następującą treścią:
#!/bin/bash
export DOCKER_REGISTRY_DOMAIN={{:DOCKER_REGISTRY_DOMAIN}}
export DOCKER_TOKEN={{:DOCKER_TOKEN}}
cat <<EOT >> ~/.bashrc
export DOCKER_REGISTRY_DOMAIN={{:DOCKER_REGISTRY_DOMAIN}}
export DOCKER_TOKEN={{:DOCKER_TOKEN}}
EOT
cat <<EOT >> ~/.tmux.conf
set -g terminal-overrides 'xterm*:smcup@:rmcup@'
# remap prefix from 'C-b' to 'C-a'
unbind C-b
set-option -g prefix C-a
bind-key C-a send-prefix
# switch panes using Alt-arrow without prefix
bind -n M-Left select-pane -L
bind -n M-Right select-pane -R
bind -n M-Up select-pane -U
bind -n M-Down select-pane -D
# Enable mouse control (clickable windows, panes, resizable panes)
#set -g mouse-select-window on
#set -g mouse-select-pane on
#set -g mouse-resize-pane on
# Enable mouse mode (tmux 2.1 and above)
set -g mouse on
set -g pane-border-format "#{pane_index}#{?@custom_pane_title,:#{@custom_pane_title},}:#{pane_title}"
bind e set-window-option synchronize-panes
EOT
sudo systemctl disable snapd.service
sudo systemctl disable snapd.socket
sudo systemctl disable snapd.seeded.service
sudo apt autoremove --purge snapd -y
sudo rm -rf /var/cache/snapd/
rm -rf ~/snap
echo "${DOCKER_TOKEN}" | docker login -u "${DOCKER_TOKEN}" "${DOCKER_REGISTRY_DOMAIN}" --password-stdin;
apt -y update
apt -y install python3 python3-pip
pip3 install docker docker-compose
git clone --recurse-submodules https://github.com/evertramos/nginx-proxy-automation.git ~/proxy && cd ~/proxy/bin && ./fresh-start.sh --yes -e [email protected] --use-nginx-conf-files
Token osobisty Digital Ocean
Teraz musimy wybrać klucze ssh do serwera. Aby to zrobić, potrzebujemy doctl
- interfejsu wiersza poleceń dla digital ocean. Na tej platformie możesz mieć konto, które należy do różnych zespołów (kontekstów). Aby wyświetlić konteksty, użyj
doctl auth ls
Możemy wybrać kontekst przez:
doctl auth switch --context preciselab
Sprawdźmy, czy mamy prawidłowy dostęp:
doctl account get
jeśli zobaczysz błąd, możemy zalogować się za pomocą tokenu
doctl auth init
Będziesz potrzebować tokena, który można wygenerować w zakładce API
w panelu Digital Ocean
Ten sam token powinien być użyty do połączenia pulumi z Twoim Digital Ocean:
pulumi config set digitalocean:token TOKEN_VALUE --secret
Klucze SSH
W tym zespole musisz przechowywać swoje klucze SSH. Aby je wyświetlić, użyj:
doctl compute ssh-key list
w nowym projekcie będzie to pusta lista, więc utwórz klucze. Jeśli chcesz oddzielić klucze w CI/CD od swoich osobistych, jako nazwę pliku możesz użyć swojego projektu.
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/id_ed25519
dodaj klucze publiczne do ustawień zabezpieczeń w Digital Ocean:
Po dodaniu możesz ponownie zapytać o klucze, ponieważ będziesz potrzebować tych numerów zwróconych przez polecenie:
doctl compute ssh-key list
Konfiguracja Pulumi index.ts
Aby używać importu bez gwiazdek, musimy dodać dwie compilerOptions
do twojego tsconfig.json
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
A na koniec crème de la crème - plik index.ts
import 'dotenv/config'
import * as pulumi from "@pulumi/pulumi";
import * as digitalocean from "@pulumi/digitalocean";
import fs from 'fs';
import {templates} from 'jsrender';
// we reading template ( there are no secretes )
const userDataTemplate = fs.readFileSync('./userData.sh').toString();
// secretes are injected during render
const userData = templates(userDataTemplate).render(process.env)
// this is our default droplet config
const dropletConfig = {
image: "docker-20-04",
region: "fra1",
size: "s-1vcpu-1gb-intel",
monitoring: true,
backups: false,
userData, // there is rendered script to initiate server
sshKeys: [
// here are ids of keys saved on digital ocean
'36403277', // Github Actions
'34336169', // Daniel Laptop
]
};
// now we defining that we need two servers
const dropletApi = new digitalocean.Droplet("xue-api", dropletConfig);
const dropletUi = new digitalocean.Droplet("xue-ui", dropletConfig);
// and single main domain
const domain = new digitalocean.Domain("xue.academy", {
name: "xue.academy",
}, {deleteBeforeReplace: true});
// two environments:
// a) production without subdomain
// b) staging with subdomain stag
const envs:Array<{name: 'stag' | 'prod', subdomain: string}> = [{
subdomain: '',
name: 'prod'
}, {
subdomain: 'stag',
name: 'stag'
}]
// there are helpers function to name resources correctly
function recordName(envName: 'prod' | 'stag', name: string) {
return envName === 'prod' ? name : `${name}-${envName}`;
}
// and simplify subdomain selecton
function subdomainName(envName: 'prod' | 'stag', name: string) {
return envName === 'prod' ? name : (name === '@' ? `${envName}` : `${name}.${envName}`);
}
// we can iterate over environments connecting domains with servers
for(let env of envs) {
const dnsUi = new digitalocean.DnsRecord(recordName(env.name, `xue.academy`), {
name: subdomainName(env.name, "@"),
domain: domain.id,
value: dropletUi.ipv4Address,
type: 'A'
}, {deleteBeforeReplace: true})
const dnsApi = new digitalocean.DnsRecord(recordName(env.name, "api.xue.academy"), {
name: subdomainName(env.name, "api"),
domain: domain.id,
value: dropletApi.ipv4Address,
type: 'A'
}, {deleteBeforeReplace: true})
}
// Export the name of the domain
export const ipUi = dropletUi.ipv4Address;
export const ipApi = dropletApi.ipv4Address;
To jest super prosta konfiguracja, ale możemy łatwo dodać bazę danych mysql.
const mysql = new digitalocean.DatabaseCluster("mysql", {
engine: "mysql",
nodeCount: 1,
region: "fra1",
size: "db-s-1vcpu-1gb",
version: "8",
}, {parent: playground});
const coreDatabase = new digitalocean.DatabaseDb("database-core", {clusterId: mysql.id});
export const mysqlId = mysql.id;
export const mysqlHost = mysql.host;
export const mysqlUser = mysql.user;
export const mysqlPassword = mysql.password;
export const mysqlPort = mysql.port;
export const mysqlUri = mysql.uri;
lub instancja redis
const redis = new digitalocean.DatabaseCluster("redis", {
engine: "redis",
nodeCount: 1,
region: "fra1",
size: "db-s-1vcpu-1gb",
version: "6",
}, {parent: playground});
export const redisHost = redis.host;
export const redisUser = redis.user;
export const redisPassword = redis.password;
export const redisPort = redis.port;
export const redisUri = redis.uri;
Mam nadzieję, że ten artykuł pomoże Ci zacząć z pulumi na Digital Ocean i przestrzegać podstawowej zasady devops:
Lepiej spędzić 10 godzin na nieudanym automatyzowaniu, niż robić zadanie ręcznie.
Podsumujmy zalety podejścia IaC.
- bardziej niezawodne zarządzanie infrastrukturą
- możesz używać tego samego procesu zatwierdzania / testowania co dla kodu do infrastruktury
- łatwo możesz mnożyć środowiska bez klikania w panele administracyjne dostawców
- migracje projektu do konta klienta niezależne od mechanizmu migracji dostawcy infrastruktury
Other articles
You can find interesting also.
Ile rodzin zmieści się w samolocie - zadanie z algorytmiki
Porównujemy dwa rozwiązania zadania polegającego na zliczaniu wolnych zestawów przyległych miejsc. Dowiesz się jak używać Profilowania i jak wielką różnicę robi użycie pop oraz shift na tablicach w js.
Daniel Gustaw
• 12 min read
Jak zainstalować Yay na czystym obrazie Dockera Arch Linux
Instalacja yay wymaga kilku kroków, takich jak tworzenie użytkownika, instalacja base-devel i git, zmiana w /etc/sudoers, klonowanie repozytorium yay i uruchomienie makepkg na nim. Ten post opisuje ten proces krok po kroku.
Daniel Gustaw
• 3 min read
Implementacja Rust RFC 7396 - Łatka JSON Merge
Prędkość i niezawodność Rust sprawiają, że jest idealny do implementacji JSON Merge Patch, zgodnie z definicją w RFC 7396. Ta specyfikacja umożliwia efektywne i bezpieczne częściowe aktualizacje dokumentów JSON.
Daniel Gustaw
• 10 min read