Zacytuję klasyka „ludzie dzielą się na tych co robią backup, oraz tych co zaczną”. Wiem że jest to frazes oklepany od lat, lecz nie mogłem się powstrzymać i go nie dodać. Podzielę się zatem moim rozwiązaniem tej kwestii, oraz dlaczego tak, a nie inaczej.
Mam sobie repozytorium w pełni prywatne w serwisie GitLab które odpowiada za konfigurację całej infrastruktury (skrypty Ansible, pliki docker-compose dla dockera, szablony konfiguracji sieci VPN). Jest to jedno z tych repozytoriów które powinno być antywzorcem, gdyż zawiera nazwy instancji, adresy IP, klucze prywatne do sieci VPN (nie próbujcie tego w domu!) i tego typu bałagan. Prawda jest taka, że przechowywanie tego w zmiennych GitLab-a, a następnie aktualizowanie wartości na lokalnym sprzęcie, to nie jest moje ulubione zajęcie i wystarczy, że sklep muszę w ten sposób ogarniać (myślę że dałoby się to lepiej rozwiązać i synchronizować chociażby pliki dotenv ze zmiennymi na GitLab-ie). Jednak nie o tym chciałem mówić. Repozytorium to ma jeszcze jedno zadanie. Posiada skonfigurowane zmienne, aby dostać się do mojej głównej bazy danych oraz konta AWS z uprawnieniami do synchronizacji plików na S3. Jedyne czego brakuje to nazwy użytkownika, hasła, nazwy bazy danych oraz nazwy bucketu na który ma zostać przesłana kopia zapasowa. Te rzeczy konfigurowane są przez „Pipeline Schedules” (każde zaplanowane zadanie może posiadać swoje zmienne które zostaną przekazane do pipeline).

Zadania te są uruchamiane codziennie o 3 rano, ale ten harmonogram może być skonfigurowany indywidualnie do każdego zadania przy użyciu wyrażenia CRON. Jednak co się dzieje kiedy zadanie jest uruchamiane?
Krok pierwszy – uruchamiamy backup
Checking out 292cfe10 as master...
Removing database-backup/
Skipping Git submodules setup
Executing "step_script" stage of the job script
00:06
Using docker image sha256:3a25dc0f17778b385ee12f40b5dadd398ec4fb710ccde51e7e4af2c9c5555d3f for mariadb:10.6.7-focal with digest mariadb@sha256:9fb960cff340126826a2f395ccb3ae1c8ca9acc63603f477db9ca2b7e2eed744 ...
$ mkdir database-backup
$ mysqldump -P ${DATABASE_PORT} -h ${DATABASE_HOST} -u ${DATABASE_USER} -p${DATABASE_PASSWORD} ${DATABASE_NAME} > database-backup/${DATABASE_NAME}-$(date +%Y%m%d-%H%M%S).sql
Uploading artifacts for successful job
00:04
Uploading artifacts...
database-backup/*.sql: found 1 matching files and directories
Uploading artifacts as "archive" to coordinator... 201 Created id=2465323825 responseStatus=201 Created token=4d-wgBCy
Cleaning up project directory and file based variables
00:02
Job succeeded
Logi z job-a odpowiadającego za stworzenie pliku backupu
Zadanie kopii zapasowej polega na użyciu docker-a z obrazem odpowiadającym bazie danych (oczywiście można to skonfigurować per zadanie używając do tego zmiennej z zaplanowanego zadania, lecz nie miałem takiej potrzeby). Uruchamiamy wbudowane w obraz narzędzie mysqldump
z konfiguracją do zewnętrznego hosta. Wynik operacji zapisujemy do pliku w katalogu database-backup
.
I tutaj słowem wyjaśnienia dlaczego ten plik znajduje się w podkatalogu, a nie bezpośrednio w głównym katalogu. Moje pliki backupu zawierają datę. Ta data jest ustawiana w momencie uruchomienia job-a. Dokładność tej daty jest co do sekundy, więc kolejny job nie byłby w stanie wygenerować tej samej nazwy dla pliku. Użycie zmiennych nie rozwiązało mojego problemu, ponieważ operacja formatowania daty nie została wykonana, a przekazana jako łańcuch znaków.
Trzymanie pliku z dump-em w podkatalogu nie jest problemem, ponieważ inicjalnie jest on pusty (zobaczcie mkdir database-backup
). Zawsze będzie tam jeden plik, który będzie przekazany jako artefakt do kolejnego kroku.
Krok drugi – przesyłamy backup na s3
Checking out 292cfe10 as master...
Removing database-backup/
Skipping Git submodules setup
Downloading artifacts
00:03
Downloading artifacts for backup_database (2465323825)...
Downloading artifacts from coordinator... ok id=2465323825 responseStatus=200 OK token=fU6aP1zJ
Executing "step_script" stage of the job script
00:05
Using docker image sha256:721c6a029e7a106d25a0d5fb0ea9760aba557738ec9c3fc637c7cda6a46c1c6a for amazon/aws-cli with digest amazon/aws-cli@sha256:dae17bc08fa2b444691e38a23a5e6499c79d33cde2c10bfd0bc2be9a061659b6 ...
$ aws s3 cp ./database-backup s3://$AWS_BUCKET_NAME/database-backup --recursive
upload: database-backup/a-tu-jest-nazwa-bazy-20220517-050306.sql to s3://a-tu-jest-nazwa-bucketu/database-backup/a-tu-jest-nazwa-bazy-20220517-050306.sql
Cleaning up project directory and file based variables
00:01
Job succeeded
Logi z job-a odpowiadającego za synchronizację backupu z s3
Tutaj sprawa jest jeszcze prostsza, ponieważ aws-cli
posiada dedykowany obraz gotowy do wykorzystania w konfiguracji pipeline GitLab-a. Korzystając z niego wystarczy wykonać polecenie aws s3 cp ./database-backup
, w wyniku czego wszystkie pliki z podkatalogu zostaną przesłane na s3 (czyli jeden plik SQL).
Jak nie uruchamiać tych zadań podczas operacji na branchu?
Kwestia z którą męczyłem się przez kilkanaście dobrych minut. Zawsze jak robiłem cokolwiek na gałęzi która jest podłączona do zaplanowanego zadania, to uruchamiał się backup. Próbowałem sprawdzać różne warunki pochodzące z predefiniowanych zmiennych, lecz na darmo. Ostatecznie skończyłem na zmiennej zdefiniowanej w zaplanowanym zadaniu IS_BACKUP_JOB=true
. Dzięki temu mogłem sprawdzić ten warunek podczas uruchamiania pipeline.
.backup-rules:
rules:
- if: $IS_BACKUP_JOB == "true"
Te reguły można dodać do każdego job-a używając słowa extends
A kto to uruchamia?
Jak wiadomo jakaś instancja gitlab-runner-a musi takie zadania wykonać. W tym przypadku zadania wykonuje mój niewykorzystywany do tej pory MacBook, który buduje obrazy dockerowe, wykorzystuje je (tak jak w tym przypadku) i ma dostęp do zewnętrznego dockera (przez TCP) do publikowania nowych serwisów w docker swarm. Jak na razie sprawdza się to świetnie!
Czego jeszcze brakuje?
Brakuje na pewno powiadomienia o powodzeniu/niepowodzeniu wykonania backupu, lecz na chwilę obecną sam odpowiadam za monitorowanie działania skryptów. Nie wykluczam dodania tego usprawnienia w przyszłości. Na chwilę obecną jednak skupiam się na ważniejszych kwestiach o których na pewno będziecie mieli okazję poczytać. Do następnego!