CI / CD
Grundaufbau

Begriffe
Continuous Integration
- Ziel: Steigerung der Software-Qualität über permanente Integration der einzelnen Bestandteile
- früher
- Erstellung der Software in sehr grossen Zeitabständen kurz vor Auslieferung
- Continuous Integration
- Erstellung und Testung in kleinen Zyklen
- → frühzeitige Erkennung von Integrationsproblemen und fehlerhaften Tests
- kleine Deltas zwischen einzelnen Builds → leichteres Finden und Beheben von Fehlern
Continuous Delivery
- Ziel: Codebasis, die jederzeit in Produktivumgebung bereitgestellt werden kann
- Freigabe produktionsreife Build an Code-Repository
- Eigenschaften
- mehr inkrementelle Updates für Anwendungen in Produktion → Reduzierung von Kosten, Zeitaufwand und Risiko bei
Bereitstellung von Änderungen
- Beispiel: “Nightly” Builds
- wichtig für kontinuierliche Bereitstellung: unkomplizierter und wiederholbarer Bereitstellungsprozess
- mehr inkrementelle Updates für Anwendungen in Produktion → Reduzierung von Kosten, Zeitaufwand und Risiko bei
Bereitstellung von Änderungen
- Bereitstellung eines Artefaktes
- Beispiele
- Firefox Nightly Release
- Chrome Canary
Continuous Deployment
- automatische Auslieferung der Software auf Produktiv-Infrastruktur
- in Praxis: App-Änderungen eines Entwicklers können binnen weniger Minuten nach Erstellung live gehen (wenn sie die automatischen Tests bestehen)
Build
- Aktionen
- bezieht Quellcode aus Repository
- stellt Verknüpfungen zu relevanten Bibliotheken, Modulen und Abhängigkeiten her
- kompiliert/baut alle Komponenten zu ausführbaren Applikation
- Übersetzung der Applikation in verpackte oder einsatzfähige Ausführumgebung
- z. B. in VM oder Container
Test
- können während oder nach erfolgreichem Abschluss des Builds stattfinden
- Arten
- Unit-Tests: Validierung von neuen Merkmalen und Funktionen
- Regressionstests: stellen sicher, dass Build mit anderen Anwendungen oder Diensten funktioniert
- Benutzerakzeptanztests: prüfen, ob Benutzer neuen Merkmale und Funktionen wie vorgesehen nutzen können
- …
GitLab-CI
- in GitLab integriert
- orchestriert durch GitLab Runners
- ausgeführt in Executors
- Docker (bevorzugt)
- Shell (direkt auf Servre)
- VirtualBox / Parallels
- …
GitLab Webseite
- Pipeline ansehen
- GitLab-Repository > Build >Jobs > #123456: my-job
- möglicher Output:
Running with gitlab-runner 18.0.3 (4e717029)
on docker+machine jZ893qmd, system ID: r_KX7VbDpZ018a
Resolving secrets
Preparing the "docker+machine" executor
00:02
Using Docker executor with image alpine:3 ...
Using effective pull policy of [always] for container alpine:3
Pulling docker image alpine:3 ...
Using docker image sha256:cea2ff433c610f5363017404ce989632e12b953114fefc6f597a58e813c15d61 for alpine:3 with digest alpine@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 ...
Preparing environment
00:01
Using effective pull policy of [always] for container sha256:2e7190a90b3766c9129a0bd54a584233df5c8cd7306a49eb59e4d7f81a29eede
Running on runner-jz893qmd-project-15290-concurrent-0 via runner-jz893qmd-gitlab-1750145039-82fd3f38...
Getting source from Git repository
00:01
Fetching changes with git depth set to 20...
Initialized empty Git repository in /builds/uek/210/sa2023_210_3/ssteil/first-pipeline/.git/
Created fresh repository.
Checking out fc43b596 as detached HEAD (ref is main)...
Skipping Git submodules setup
Executing "step_script" stage of the job script
00:00
Using effective pull policy of [always] for container alpine:3
Using docker image sha256:cea2ff433c610f5363017404ce989632e12b953114fefc6f597a58e813c15d61 for alpine:3 with digest alpine@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 ...
$ echo "This is my first GitLab-CI pipeline"
This is my first GitLab-CI pipeline
$ pwd
/builds/uek/210/sa2023_210_3/ssteil/first-pipeline
$ ls -al
total 24
drwxrwxrwx 3 root root 4096 Jun 17 07:41 .
drwxrwxrwx 4 root root 4096 Jun 17 07:41 ..
drwxrwxrwx 6 root root 4096 Jun 17 07:41 .git
-rw-rw-rw- 1 root root 162 Jun 17 07:41 .gitlab-ci.yml
-rw-rw-rw- 1 root root 6178 Jun 17 07:41 README.md
$ uname -a
Linux runner-jz893qmd-project-15290-concurrent-0 6.8.0-1030-gcp #32~22.04.1-Ubuntu SMP Tue Apr 29 23:17:09 UTC 2025 x86_64 Linux
Cleaning up project directory and file based variables
00:01
Job succeeded
Konfiguration: .gitlab-ci.yml
- GitLab Docs - CI/CD YAML syntax reference
- mit
.gitlab-ci.ymlim Repository - Aufteilung in Stages → enthalten Jobs
- getriggert durch Rules
- definiert Ausführungsumgebung (z. B. Docker-Image)
- definiert, welche Resultate (Tests, Binaries, …) als Artefakte gespeichert werden sollen
Aufbau:
stages- Gruppierung von Jobs
Rules (GitLab Docs - Specify when jobs run with rules)
Beispiel:
stages:
- build
- test
- deliver
- deploy
default:
image: alpine:3
job1:
stage: build
script:
- echo "Executed on all branches"
job2:
stage: build
rules:
- if: $CI_COMMIT_BRANCH == 'production'
script:
- echo "Executed only on the production branch"
job3:
stage: build
rules:
- if: $CI_COMMIT_BRANCH == 'main' || $CI_COMMIT_BRANCH == 'staging'
script:
- echo "Executed on the branches main and staging"
Pipeline mit Azure
-
GitLab-Projekt erstellen
cd existing_folder
git init --initial-branch=main
git remote add origin <path-to-gitlab-repository>
git add .
git commit -m "Initial commit"
git push -u origin main -
Umgebungsvariablen in CI/CD-Einstellungen definieren
- AZURE_CLI_APPID
- AZURE_CLI_PASSWORD
- AZURE_CLI_TENANT
-
Azure Ressourcen erstellen, welche nicht im
.gitlab-ci.ymlerstellt werden (siehe Spring App mit Docker Compose)az provider register --namespace Microsoft.ContainerRegistry
az acr create --resource-group bbcssteilContainerRegistryRG --name bbcssteilcr --sku BasicDocker Engine (über Docker Desktop) starten
az acr login --name bbcssteilcr
az acr show --name bbcssteilcr --resource-group bbcssteilContainerRegistryRG --query id
az ad sp create-for-rbac --scopes /subscriptions/c912ae16-0c3b-47bc-a267-743415032e19/resourceGroups/bbcssteilContainerRegistryRG/providers/Microsoft.ContainerRegistry/registries/bbcssteilcr --role 'Owner' -
Beispiel für
.gitlab-ci.ymlvariables:
RG_NAME:
value: "uek-210"
LOCATION:
value: "switzerlandnorth"
CONTAINER_REGISTRY:
value: "bbcssteilcr"
IMAGE_NAME:
value: studle
stages:
- build
- deploy
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker login -u $AZURE_CLI_APPID -p $AZURE_CLI_PASSWORD $CONTAINER_REGISTRY.azurecr.io
- docker build -t studle .
- docker tag studle $CONTAINER_REGISTRY.azurecr.io/studle:latest
- docker push $CONTAINER_REGISTRY.azurecr.io/studle:latest
- echo "docker container studle was built and pushed"
- echo "docker container mariadb was pushed"
deploy:
stage: deploy
image: mcr.microsoft.com/azure-cli:latest
script:
- az login --service-principal -u ${AZURE_CLI_APPID} -p ${AZURE_CLI_PASSWORD} --tenant ${AZURE_CLI_TENANT}
- az provider register --namespace Microsoft.ContainerRegistry
- az container create --resource-group bbcssteilContainerRegistryRG --file deploy-aci.yaml --registry-login-server ${CONTAINER_REGISTRY}.azurecr.io --registry-username ${AZURE_CLI_APPID} --registry-password ${AZURE_CLI_PASSWORD} -
Committen und auf GitLab pushen
Pipeline mit Azure und BICEP
-
Bicep-Datei erstellen
-
deploy-studle-app.bicepparam name string = 'azure-studle'
param location string = resourceGroup().location
param image string
param registry string
param username string
@secure()
param password string
resource containerGroups_azure_studle_name_resource 'Microsoft.ContainerInstance/containerGroups@2022-10-01-preview' = {
name: name
location: location
tags: {
'docker-compose-application': 'docker-compose-application'
}
properties: {
sku: 'Standard'
containers: [
{
name: 'app'
properties: {
image: '${registry}/${image}:latest'
ports: [
{
protocol: 'TCP'
port: 80
}
]
environmentVariables: [
{
name: 'SERVER_PORT'
value: '80'
}
{
name: 'SPRING_DATASOURCE_URL'
value: 'jdbc:mariadb://db:3306/studle'
}
]
resources: {
requests: {
memoryInGB: 1
cpu: 1
}
limits: {
memoryInGB: 1
cpu: 1
}
}
}
}
{
name: 'db'
properties: {
image: 'mariadb:10.9'
ports: []
environmentVariables: [
{
name: 'MARIADB_ROOT_PASSWORD'
value: 'password'
}
{
name: 'MARIADB_DATABASE'
value: 'studle'
}
]
resources: {
requests: {
memoryInGB: 1
cpu: 1
}
limits: {
memoryInGB: 1
cpu: 1
}
}
}
}
{
name: 'aci--dns--sidecar'
properties: {
image: 'docker/aci-hostnames-sidecar:1.0'
command: [
'/hosts'
'app'
'db'
]
ports: []
environmentVariables: []
resources: {
requests: {
memoryInGB: '0.1'
cpu: '0.01'
}
}
}
}
]
initContainers: []
imageRegistryCredentials: [
{
server: registry
username: username
password: password
}
]
restartPolicy: 'Always'
ipAddress: {
ports: [
{
protocol: 'TCP'
port: 80
}
]
type: 'Public'
}
osType: 'Linux'
}
}
-
-
.gitlab-ci.ymlanpassen-
Es werden dabei zwei zusätzliche Zeilen benötigt (unteren zwei):
script:
- az login --service-principal -u ${AZURE_CLI_APPID} -p ${AZURE_CLI_PASSWORD} --tenant ${AZURE_CLI_TENANT}
- az provider register --name Microsoft.ContainerInstance
- az deployment group create --resource-group bbcssteilContainerRegistryRG --template-file deploy-studle-app.bicep --parameters image=studle registry=$CONTAINER_REGISTRY.azurecr.io username=$AZURE_CLI_APPID password=$AZURE_CLI_PASSWORD
variables:
RG_NAME:
value: "uek-210"
LOCATION:
value: "switzerlandnorth"
CONTAINER_REGISTRY:
value: "bbcssteilcr"
IMAGE_NAME:
value: studle
stages:
- build
- deploy
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker login -u $AZURE_CLI_APPID -p $AZURE_CLI_PASSWORD $CONTAINER_REGISTRY.azurecr.io
- docker build -t studle .
- docker tag studle $CONTAINER_REGISTRY.azurecr.io/studle:latest
- docker push $CONTAINER_REGISTRY.azurecr.io/studle:latest
- echo "docker container studle was built and pushed"
- echo "docker container mariadb was pushed"
deploy:
stage: deploy
image: mcr.microsoft.com/azure-cli:latest
script:
- az login --service-principal -u ${AZURE_CLI_APPID} -p ${AZURE_CLI_PASSWORD} --tenant ${AZURE_CLI_TENANT}
- az provider register --name Microsoft.ContainerInstance
- az deployment group create --resource-group bbcssteilContainerRegistryRG --template-file deploy-studle-app.bicep --parameters image=studle registry=$CONTAINER_REGISTRY.azurecr.io username=$AZURE_CLI_APPID password=$AZURE_CLI_PASSWORD -
-
Repository committen und pushen
Infrastructure as Code mit Azure
-
in GitLab neues Projekt ohne
README.mderstellen -
existierenden Ordner ins GitLab-Repository pushen (siehe in GitLab-Projekt unter Push an existing folder)
cd existing_folder
git init --initial-branch=main
git remote add origin <path-to-gitlab-repository>
git add .
git commit -m "Initial commit"
git push -u origin main -
Service Principal erstellen (siehe
az ad sp- Service Principal)az ad sp create-for-rbac --scopes /subscriptions/c912ae16-0c3b-47bc-a267-743415032e19 --role 'Owner'Output:
{
"appId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"displayName": "azure-cli-2025-06-17-12-07-29",
"password": "9Ailu~u.2lJe2jk2f5=tQEA8o.fhq4_dQrHncIo2",
"tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
} -
GitLab-Variablen erstellen
- AZURE_CLI_APPID
- AZURE_CLI_PASSWORD
- AZURE_CLI_TENANT
-
.gitlab-ci.ymlerstellenvariables:
RG_NAME:
value: "uek-210"
LOCATION:
value: "switzerlandnorth"
CONTAINER_REGISTRY:
value: "ssteilregistry"
IMAGE_NAME:
value: studle
stages:
- build
- deploy
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker login -u $AZURE_CLI_APPID -p $AZURE_CLI_PASSWORD $CONTAINER_REGISTRY.azurecr.io
- docker build -t studle .
- docker tag studle $CONTAINER_REGISTRY.azurecr.io/studle:latest
- docker push $CONTAINER_REGISTRY.azurecr.io/studle:latest
- echo "docker container studle was built and pushed"
- echo "docker container mariadb was pushed"
deploy:
stage: deploy
image: mcr.microsoft.com/azure-cli:latest
script:
- az login --service-principal -u ${AZURE_CLI_APPID} -p ${AZURE_CLI_PASSWORD} --tenant ${AZURE_CLI_TENANT}
- az provider register --name Microsoft.ContainerInstance
- az deployment group create --resource-group bbcssteilContainerRegistryRG --template-file deploy-studle-app.bicep --parameters image=studle registry=$CONTAINER_REGISTRY.azurecr.io username=$AZURE_CLI_APPID password=$AZURE_CLI_PASSWORD -
Bicep-Datei erstellen
-
deploy-studle-app.bicepparam name string = 'azure-studle'
param location string = resourceGroup().location
param image string
param registry string
param username string
@secure()
param password string
resource containerGroups_azure_studle_name_resource 'Microsoft.ContainerInstance/containerGroups@2022-10-01-preview' = {
name: name
location: location
tags: {
'docker-compose-application': 'docker-compose-application'
}
properties: {
sku: 'Standard'
containers: [
{
name: 'app'
properties: {
image: '${registry}/${image}:latest'
ports: [
{
protocol: 'TCP'
port: 80
}
]
environmentVariables: [
{
name: 'SERVER_PORT'
value: '80'
}
{
name: 'SPRING_DATASOURCE_URL'
value: 'jdbc:mariadb://db:3306/studle'
}
]
resources: {
requests: {
memoryInGB: 1
cpu: 1
}
limits: {
memoryInGB: 1
cpu: 1
}
}
}
}
{
name: 'db'
properties: {
image: 'mariadb:10.9'
ports: []
environmentVariables: [
{
name: 'MARIADB_ROOT_PASSWORD'
value: 'password'
}
{
name: 'MARIADB_DATABASE'
value: 'studle'
}
]
resources: {
requests: {
memoryInGB: 1
cpu: 1
}
limits: {
memoryInGB: 1
cpu: 1
}
}
}
}
{
name: 'aci--dns--sidecar'
properties: {
image: 'docker/aci-hostnames-sidecar:1.0'
command: [
'/hosts'
'app'
'db'
]
ports: []
environmentVariables: []
resources: {
requests: {
memoryInGB: 1
cpu: 1
}
}
}
}
]
initContainers: []
imageRegistryCredentials: [
{
server: registry
username: username
password: password
}
]
restartPolicy: 'Always'
ipAddress: {
ports: [
{
protocol: 'TCP'
port: 80
}
]
type: 'Public'
}
osType: 'Linux'
}
}
-
-
Repository committen und pushen