Nos últimos anos, passei a gostar muito do GitLab CI . Principalmente por sua simplicidade e funcionalidade. Basta criar um arquivo na raiz do repositório .gitlab-ci.yml
, adicionar algumas linhas de código lá e o próximo commit iniciará o pipeline com um conjunto de jobs que executarão os comandos especificados.
E se você adicionar a inclusão e estender recursos para isso , você pode fazer coisas bastante interessantes: criar trabalhos de modelo e pipelines, movê-los para repositórios separados e reutilizá-los em projetos diferentes sem copiar o código.
Mas, infelizmente, nem tudo é tão róseo como gostaríamos. A instrução script
no GitLab CI é de nível muito baixo. Ele simplesmente executa os comandos que lhe são fornecidos na forma de strings. Não é muito conveniente escrever scripts grandes dentro de YAML. Conforme a lógica se torna mais complexa, o número de scripts aumenta, eles se misturam com YAML tornando as configurações ilegíveis e complicando sua manutenção.
Eu realmente senti falta de algum mecanismo que simplificasse o desenvolvimento de grandes scripts. Como resultado, nasceu um microframework para o desenvolvimento do GitLab CI, sobre o qual quero falar neste artigo (usando o exemplo de um pipeline simples para construir imagens docker).
Exemplo: construir imagens docker no GitLab
Quero considerar o processo de criação de um pipeline usando um exemplo de uma tarefa simples que sempre encontrei com diferentes equipes: a criação de imagens docker básicas no GitLab para reutilização.
Por exemplo, uma equipe escreve microsserviços e deseja usar sua própria imagem de base para todos eles com um conjunto de utilitários de depuração pré-instalados.
Ou outro exemplo, uma equipe escreve testes e deseja usar serviços diretamente no GitLab para criar um banco de dados temporário (ou fila, ou outra coisa) para eles usando sua própria imagem.
, docker- GitLab . .gitlab-ci.yml
:
services:
- docker:dind
Build:
image: docker
script:
- |
docker login "$CI_REGISTRY" \
--username "$CI_REGISTRY_USER" \
--password "$CI_REGISTRY_PASSWORD"
docker build \
--file "Dockerfile" \
--tag "$CI_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_REF_SLUG" .
docker push "$CI_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_REF_SLUG"
Build
, GitLab Container Registry, . , ( , ).
, , :
,
dockerfiles
.
:
Build All
-dockerfiles
. ( ).
Build Changed
-dockerfiles
, . ( ) .
. . , .
:
, .NET.
:
docker-, Container Registry :
, .
1: " "
. .gitlab-ci.yml
. :
services:
- docker:dind
stages:
- Build
Build All:
stage: Build
image: docker
when: manual
script:
- |
dockerfiles=$(find "dockerfiles" -name "*.Dockerfile" -type f)
docker login "$CI_REGISTRY" \
--username "$CI_REGISTRY_USER" \
--password "$CI_REGISTRY_PASSWORD"
for dockerfile in $dockerfiles; do
path=$(echo "$dockerfile" | sed 's/^dockerfiles\///' | sed 's/\.Dockerfile$//')
tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"
echo "Building $dockerfile..."
docker build --file "$dockerfile" --tag "$tag" .
echo "Pushing $tag..."
docker push "$tag"
done
Build Changed:
stage: Build
image: docker
only:
changes:
- 'dockerfiles/*.Dockerfile'
- 'dockerfiles/**/*.Dockerfile'
script:
- |
apk update
apk add git # , , ...
dockerfiles=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')
docker login "$CI_REGISTRY" \
--username "$CI_REGISTRY_USER" \
--password "$CI_REGISTRY_PASSWORD"
for dockerfile in $dockerfiles; do
path=$(echo "$dockerfile" | sed 's/^dockerfiles\///' | sed 's/\.Dockerfile$//')
tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"
echo "Building $dockerfile..."
docker build --file "$dockerfile" --tag "$tag" .
echo "Pushing $tag..."
docker push "$tag"
done
:
:
Build All
, :
, ..
when: manual
.
:
find "dockerfiles" -name "*.Dockerfile" -type f
Build Changed
, :
, ..
only:changes
.
, :
git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile'
, , ,
dockerfiles/
.Dockerfile
, GitLab Container Registry.
:
( )
, .
GitLab CI .
GitLab CI Bootstrap
GitLab CI Bootstrap - GitLab CI. :
(
.gitlab-ci.yml
) (bash shell), YAML .
, .
( ) , .
GitLab CI Bootstrap bootstrap.gitlab-ci.yml, .gitlab-ci.yml
include. . include:local:
include:
- local: 'bootstrap.gitlab-ci.yml'
.bootstrap
, :
.bootstrap:
before_script:
- |
...
.bootstrap
, extends:
example:
extends: '.bootstrap'
script:
- '...'
( ), GitLab.
bash shell. git, . .bootstrap
docker- .
, , docker-.
2:
, .bootstrap
.gitlab-ci.sh
. , .
, , .gitlab-ci.yml
:
.gitlab-ci.yml
:
include:
- project: '$CI_PROJECT_NAMESPACE/bootstrap'
ref: 'master'
file: 'bootstrap.gitlab-ci.yml'
services:
- docker:dind
stages:
- Build
Build All:
stage: Build
image: docker
extends: .bootstrap
when: manual
script:
- search_all_dockerfiles_task
- build_and_push_dockerfiles_task
Build Changed:
stage: Build
image: docker
extends: .bootstrap
only:
changes:
- 'dockerfiles/*.Dockerfile'
- 'dockerfiles/**/*.Dockerfile'
script:
- install_git_task
- search_changed_dockerfiles_task
- build_and_push_dockerfiles_task
.gitlab-ci.sh
:
DOCKERFILES=""
function search_all_dockerfiles_task() {
DOCKERFILES=$(find "dockerfiles" -name "*.Dockerfile" -type f)
}
function search_changed_dockerfiles_task() {
DOCKERFILES=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')
}
function install_git_task() {
# , , ...
apk update
apk add git
}
function build_and_push_dockerfiles_task() {
docker login "$CI_REGISTRY" \
--username "$CI_REGISTRY_USER" \
--password "$CI_REGISTRY_PASSWORD"
for dockerfile in $DOCKERFILES; do
path=$(echo "$dockerfile" | sed 's/^dockerfiles\///' | sed 's/\.Dockerfile$//')
tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"
echo "Building $dockerfile..."
docker build --file "$dockerfile" --tag "$tag" .
echo "Pushing $tag..."
docker push "$tag"
done
}
:
( step2): https://gitlab.com/chakrygin/dockerfiles-example/-/tree/step2
: https://gitlab.com/chakrygin/dockerfiles-example/-/pipelines/303676828
gitlab-ci.sh
, .gitlab-ci.yml
. search_all_dockerfiles_task
search_changed_dockerfiles_task
DOCKERFILES
, build_and_push_dockerfiles_task
.
3:
, , . , , . , - , , .
. , , , - , . , .
, .bootstrap
CI_IMPORT
, : CI_{module}_PROJECT
, CI_{module}_REF
CI_{module}_FILE
, {module}
- CI_IMPORT
( ).
, .gitlab-ci.sh
, .
:
.gitlab-ci.yml
:
include:
- project: '$CI_PROJECT_NAMESPACE/dockerfiles-example-ci'
ref: 'step3'
file: 'dockerfiles.gitlab-ci.yml'
:
dockerfiles.gitlab-ci.yml
( .gitlab-ci.yml
):
include:
- project: '$CI_PROJECT_NAMESPACE/bootstrap'
ref: 'master'
file: 'bootstrap.gitlab-ci.yml'
services:
- docker:dind
stages:
- Build
variables:
CI_DOCKERFILES_PROJECT: '$CI_PROJECT_NAMESPACE/dockerfiles-example-ci'
CI_DOCKERFILES_REF: 'step3'
CI_DOCKERFILES_FILE: 'dockerfiles.gitlab-ci.sh'
Build All:
stage: Build
image: docker
extends: .bootstrap
variables:
CI_IMPORT: dockerfiles
when: manual
script:
- search_all_dockerfiles_task
- build_and_push_dockerfiles_task
Build Changed:
stage: Build
image: docker
extends: .bootstrap
variables:
CI_IMPORT: dockerfiles
only:
changes:
- 'dockerfiles/*.Dockerfile'
- 'dockerfiles/**/*.Dockerfile'
script:
- search_changed_dockerfiles_task
- build_and_push_dockerfiles_task
dockerfiles.gitlab-ci.sh
( .gitlab-ci.sh
):
DOCKERFILES=""
function search_all_dockerfiles_task() {
DOCKERFILES=$(find "dockerfiles" -name "*.Dockerfile" -type f)
}
function search_changed_dockerfiles_task() {
DOCKERFILES=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')
}
function build_and_push_dockerfiles_task() {
docker login "$CI_REGISTRY" \
--username "$CI_REGISTRY_USER" \
--password "$CI_REGISTRY_PASSWORD"
for dockerfile in $DOCKERFILES; do
path=$(echo "$dockerfile" | sed 's/^dockerfiles\///' | sed 's/\.Dockerfile$//')
tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"
echo "Building $dockerfile..."
docker build --file "$dockerfile" --tag "$tag" .
echo "Pushing $tag..."
docker push "$tag"
done
}
:
( step3): https://gitlab.com/chakrygin/dockerfiles-example/-/tree/step3
( step3): https://gitlab.com/chakrygin/dockerfiles-example-ci/-/tree/step3
: https://gitlab.com/chakrygin/dockerfiles-example/-/pipelines/303702906
Build All
Build Changed
CI_IMPORT
dockerfiles
. : CI_DOCKERFILES_PROJECT
, CI_DOCKERFILES_REF
CI_DOCKERFILES_FILE
, .
, dockerfiles.gitlab-ci.sh
, $CI_PROJECT_NAMESPACE/dockerfiles-example-ci
, step3
.
, install_git_task
. git , .. git git .
4:
. , .
dockerfiles.gitlab-ci.sh
. , .
, include
, , . , . include
, .
, , include
.
dockerfiles.gitlab-ci.sh
:
dockerfiles.gitlab-ci.sh
:
include "tasks/search.sh"
include "tasks/docker.sh"
tasks/search.sh
:
DOCKERFILES=""
function search_all_dockerfiles_task() {
DOCKERFILES=$(find "dockerfiles" -name "*.Dockerfile" -type f)
}
function search_changed_dockerfiles_task() {
DOCKERFILES=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')
}
tasks/docker.sh
:
function build_and_push_dockerfiles_task() {
docker login "$CI_REGISTRY" \
--username "$CI_REGISTRY_USER" \
--password "$CI_REGISTRY_PASSWORD"
for dockerfile in $DOCKERFILES; do
path=$(echo "$dockerfile" | sed 's/^dockerfiles\///' | sed 's/\.Dockerfile$//')
tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"
echo "Building $dockerfile..."
docker build --file "$dockerfile" --tag "$tag" .
echo "Pushing $tag..."
docker push "$tag"
done
}
:
( step4): https://gitlab.com/chakrygin/dockerfiles-example/-/tree/step4
( step4): https://gitlab.com/chakrygin/dockerfiles-example-ci/-/tree/step4
: https://gitlab.com/chakrygin/dockerfiles-example/-/pipelines/303704359
GitLab CI bash GitLab CI Bootstrap.
. :
. .. , . GitLab CI.
. , Python C#.
. , .
, .
, 1.0. , . , .
Fontes: podem ser encontradas aqui: https://gitlab.com/chakrygin/bootstrap
Repositórios com exemplos aqui: