GitLab Runner: Budowanie obrazów Docker
Efektywne budowanie i wysyłanie obrazów Docker z pipeline’ów GitLab CI/CD przy użyciu runnerów Stackhero i Docker-in-Docker
👋 Witamy w dokumentacji Stackhero!
Stackhero oferuje prostą usługę GitLab Runner cloud, która umożliwia wydajne i bezproblemowe uruchamianie zadań GitLab CI/CD. Oto, czego możesz się spodziewać:
- Nielimitowane minuty CI/CD: uruchamiaj swoje pipeline'y tak często, jak potrzebujesz, bez rozliczania za minuty i nieprzewidzianych kosztów.
- Wiele równoczesnych zadań: przyspiesz rozwój, uruchamiając kilka zadań jednocześnie.
- Docker executor z obsługą Docker-in-Docker: łatwo buduj i wysyłaj obrazy kontenerów w ramach procesu CI/CD.
- Działa bezproblemowo zarówno z GitLab.com, jak i z instancjami self-managed GitLab.
- Prywatna, dedykowana infrastruktura z szybkim dyskiem NVMe/SSD zapewnia stabilną i przewidywalną wydajność budowania.
- Dostępne w regionach 🇪🇺 Europa oraz 🇺🇸 USA, aby dopasować się do potrzeb Twojego zespołu.
Oszczędzaj czas: możesz podłączyć swojego pierwszego GitLab Runner i rozpocząć uruchamianie pipeline'ów w zaledwie kilka minut!
Wprowadzenie
W przypadku Stackhero GitLab Runner każdy job uruchamiany jest w świeżym kontenerze z wykorzystaniem Docker executor. Możesz budować własne obrazy Docker bezpośrednio w swoim pipeline, włączając Docker-in-Docker (DinD). Ta konfiguracja uruchamia demona Docker równolegle z Twoim jobem, dzięki czemu możesz wykonywać polecenia docker build oraz docker push w ramach procesu CI/CD.
Każde uruchomienie korzysta z nielimitowanych minut CI/CD: możesz budować tak często, jak potrzebujesz, bez obaw o limity wykorzystania. Twój cache buildów jest przechowywany na dedykowanym dysku runnera, co pozwala na ponowne wykorzystanie wcześniejszych warstw podczas kolejnych buildów. Znacząco skraca to czas budowania i przyspiesza realizację pipeline’ów.
Budowanie obrazu Docker z Docker-in-Docker
Możesz dodać poniższy przykładowy plik .gitlab-ci.yml do swojego repozytorium. Ta konfiguracja buduje Dockerfile znajdujący się w katalogu głównym projektu:
build-image:
stage: build
image: docker:29
services:
- name: docker:29-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
before_script:
- docker info
script:
# Zamień "my-image" na wybraną nazwę:
- docker build -t my-image .
# Opcjonalnie, uruchom szybki test na zbudowanym obrazie:
# - docker run --rm my-image /path/to/tests
W tym przykładzie używamy obrazu Docker w wersji 29. Możesz użyć nowszej wersji, jeśli jest dostępna. Najnowsze tagi znajdziesz na oficjalnej stronie obrazu Docker.
W tej konfiguracji serwis docker:29-dind uruchamia demona Docker obok Twojego joba, a DOCKER_HOST: "tcp://docker:2375" wskazuje CLI docker, aby korzystało z tego demona. Zawsze ustawiaj DOCKER_HOST, gdy deklarujesz serwis docker:dind: bez tego CLI po cichu połączy się z innym demonem, przez co build może przejść mimo błędnej konfiguracji serwisu, co ukrywa rzeczywiste problemy (i powoduje błędy w narzędziach takich jak Testcontainers, które polegają na tym serwisie). DOCKER_TLS_CERTDIR: "" oznacza połączenie przez zwykły, niezaszyfrowany port 2375 w wewnętrznej sieci joba.
Jako prostszą alternatywę możesz pominąć blok services i obie zmienne: runner Stackhero udostępnia gotowego do użycia demona Docker przez zamontowany socket, więc polecenie docker build działa bez dodatkowej konfiguracji.
Uruchamianie testów z Testcontainers
Testcontainers to biblioteka testowa dostępna dla Java, Go, Node.js, Python, .NET i innych języków, która uruchamia rzeczywiste usługi jako tymczasowe kontenery Docker podczas wykonywania testów. Zamiast mockować bazę danych czy broker wiadomości, testy integracyjne komunikują się z prawdziwą instancją PostgreSQL, MySQL, Redis lub Kafka, utworzoną przed testami i usuwaną zaraz po nich. Rozwiązanie to jest szczególnie popularne w projektach Java i Spring.
Testcontainers wymaga demona Docker, więc działa dokładnie z tą samą konfiguracją Docker-in-Docker jak powyżej. Nie są potrzebne dodatkowe zmienne:
test:
stage: test
# Użyj obrazu wymaganego przez Twoje testy (tutaj JDK), niekoniecznie obrazu docker:
image: gradle:jdk21
services:
- name: docker:29-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
script:
- gradle test
Testcontainers odczytuje DOCKER_HOST, aby znaleźć demona, i używa tej samej nazwy hosta docker, aby uzyskać dostęp do portów publikowanych przez uruchamiane kontenery. Oba mechanizmy działają niezależnie, ponieważ alias docker jest rozpoznawany w sieci joba.
Zachowaj
DOCKER_HOSTw jobach korzystających z Testcontainers. Bez tej zmiennej Testcontainers użyje socketu Docker zamontowanego przez runnera, a następnie spróbuje połączyć się z kontenerami testowymi przez adres IP hosta, do którego job nie ma dostępu. Typowym objawem jest błąd kontenera pomocniczego Ryuk:Wait strategy failed. Container is removedlubTimed out waiting for log output matching '.*Started.*'.
Wysyłanie do rejestru kontenerów GitLab
GitLab udostępnia predefiniowane zmienne (CI_REGISTRY, CI_REGISTRY_USER, CI_REGISTRY_PASSWORD, CI_REGISTRY_IMAGE), dzięki którym pipeline może się uwierzytelnić i bezpiecznie wysyłać obrazy do rejestru kontenerów projektu. Nie są wymagane żadne dodatkowe sekrety.
Oto przykładowy job budujący i wysyłający obraz:
build-and-push:
stage: build
image: docker:29
services:
- name: docker:29-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
script:
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
# Jeśli jesteś na domyślnej gałęzi, możesz również otagować i wysłać "latest":
- |
if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then
docker tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" "$CI_REGISTRY_IMAGE:latest"
docker push "$CI_REGISTRY_IMAGE:latest"
fi
Aby wysłać obrazy do innego rejestru (np. Docker Hub lub prywatnego rejestru), możesz przechowywać dane uwierzytelniające jako zmienne CI/CD i użyć ich z docker login w ten sam sposób.
Przyspieszanie powtarzających się buildów
Dysk runnera jest utrzymywany pomiędzy pipeline’ami, co pozwala na ponowne wykorzystanie warstw obrazu jako cache buildów. Dzięki temu kolejne buildy są znacznie szybsze. Oto przykładowa konfiguracja:
build-cached:
stage: build
image: docker:29
services:
- name: docker:29-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
script:
# Pobierz najnowszy obraz, aby zainicjować cache (jeśli istnieje):
- docker pull "$CI_REGISTRY_IMAGE:latest" || true
- docker build --cache-from "$CI_REGISTRY_IMAGE:latest" -t "$CI_REGISTRY_IMAGE:latest" .
- docker push "$CI_REGISTRY_IMAGE:latest"
To podejście pozwala wykorzystać cache warstw Docker, więc przebudowywane są tylko nowe lub zmienione warstwy.
Uruchamianie jobów równolegle
Twój plan określa, ile jobów może być uruchamianych jednocześnie. Joby w tej samej fazie startują razem, do limitu współbieżności. Oznacza to, że kilka niezależnych jobów może być wykonywanych równolegle i zakończyć się, gdy najwolniejszy z nich się skończy, zamiast czekać na zakończenie każdego po kolei.
Przykład:
stages:
- test
unit:
stage: test
image: node:22
script: npm run test:unit
integration:
stage: test
image: node:22
script: npm run test:integration
e2e:
stage: test
image: node:22
script: npm run test:e2e
Jeśli ustawisz współbieżność na 1 lub więcej, joby unit, integration i e2e zostaną uruchomione równocześnie.
Więcej informacji na temat budowania obrazów Docker w pipeline’ach GitLab CI/CD znajdziesz w oficjalnej dokumentacji GitLab dotyczącej używania Docker builds.