## Zadanie 1

### Docker-compose
Służy do definiowania i odpalania wielu kontenerów dockerowych równocześnie
Kluczowe punkty:
* mamy plik analogiczny do `Dockerfile`, który nazywa się `docker-compose.yml`, który definiuje potrzebne własności (usługi, sieci, wolumeny itd.)
* Uruchamia wszystsko przy pomocy jednej komendy
* Domyślnie tworzy wewnętrzną sieć, do której są podłączone wszystkie serwisy, umożliwiając łatwą komunikację
Docker Compose is a tool for running multi-container applications on Docker defined using the Compose file format. A Compose file is used to define how one or more containers that make up your application are configured. Once you have a Compose file, you can create and start your application with a single command: docker compose up.
### Live demo
```docker-compose
services:
web:
ports:
- "8081-8083:80"
image: 'nginx:latest'
deploy:
replicas: 3
```
Odpalenie (w trybie `detach`)
`docker compose up -d`
Sprawdzenie
`docker ps`
```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0ad7a00557e0 nginx:latest "/docker-entrypoint.…" 2 seconds ago Up 2 seconds 0.0.0.0:8082->80/tcp, [::]:8082->80/tcp zad1-web2-1
d7349a239a87 nginx:latest "/docker-entrypoint.…" 2 seconds ago Up 2 seconds 0.0.0.0:8081->80/tcp, [::]:8081->80/tcp zad1-web1-1
bff07d29efe2 nginx:latest "/docker-entrypoint.…" 2 seconds ago Up 2 seconds 0.0.0.0:8083->80/tcp, [::]:8083->80/tcp zad1-web3-1
```
Wstrzymanie (zobacz, że dalej widoczne w `docker ps -a`)
`docker compose stop`
Wyłączenie wszystkich i posprzątanie
`docker compose down`
### Live demo wersja pro
`docker-compose.yml`
```dockerfile=
services:
web:
ports:
- "8081-8083:80"
image: 'nginx:latest'
volumes:
- ./entrypoint.sh:/docker-entrypoint.sh
deploy:
replicas: 3
command: /docker-entrypoint.sh
app:
image: alpine:latest
command: sh -c "apk update && apk add curl && sleep infinity"
container_name: app
```
`entrypoint.sh`
```bash=
#!/bin/sh
set -eu
SERVICE_NAME=$(hostname)
cat > /usr/share/nginx/html/index.html <<EOF
<!DOCTYPE html>
<html>
<head><title>Docker Compose Test</title></head>
<body>
<h1>Hello from ${SERVICE_NAME}!</h1>
<p>This is container running Nginx/Alpine.</p>
</body>
</html>
EOF
exec nginx -g "daemon off;"
```
`nginx.conf`
```nginx=
worker_processes 1;
daemon off; # Utrzymuje Nginx na pierwszym planie, wymagane w Dockerze
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
# Ustaw zmienną Nginx na wartość zmiennej środowiskowej
# (Wymaga użycia "env" w pliku docker-compose)
set $service_name "DEFAULT_WEB";
location / {
# Zwracanie prostej odpowiedzi z wbudowaną zmienną ze skryptu entrypoint
return 200 '<h1>Hello from $SERVICE_NAME!</h1>';
add_header Content-Type text/html;
}
}
}
```
I teraz możemy odpalić
```bash=
chmod +x entrypoint.sh
docker compose up -d
docker exec -it app sh
curl http://web # I tak kilka razy żeby pokazać, że będą różne hostname'y odpowiadać
```
I ładnie widać, że mamy odpowiedzi z tych serwisów:D
## Zadanie 2

Przygotowanie:
1. echo "Tajna flaga" >> /home/flag.txt
2. chmod 0400 /home/flag.txt
3. chown root:root /home/flag.txt
4. `docker run -it --rm -v <path_to_flag>:/mnt/host ubuntu bash`
* `docker run -it --rm -v /home:/mnt/host ubuntu bash`
Gdzie
* `ubuntu` to obraz (możemy korzystać z ubuntu:22.04, latest albo alpine)
* `-v` (volume), mapujemy `<path_to_flag>` na `/mnt/host` w naszym obrazie
5. Teraz to co możemy zrobić to po prostu
`cat /mnt/host/flag.txt` i mamy flagę :D
## Zadanie 3

### Komendy
```bash=
docker build -f Dockerfile-x -t l3za:x .
docker build -f Dockerfile-y -t l3za:y .
docker run -v l3za:/root/katalog -it --rm l3za:x
docker run -v l3za:/root/katalog -it --rm l3za:x
docker run -v l3za:/root/katalog -it --rm l3za:x
docker run -v l3za:/root/katalog -it --rm l3za:y
docker run -v l3za:/root/katalog -it --rm l3za:y
docker run -v l3za:/root/katalog -it --rm l3za:x
```
### Co się stało?
Volumeny są `persistant`, czyli są niezależne od dockera. To co się pewnie dzieje to `:x` nic nie robi, a ten `:y` psuje. Ale pamiętajmy, że volumen jest taki sam, więc ten, który tylko próbuje przeczytać dalej ma problem:D
Jeśli nie mamy volumenu, to docker tworzy nowy, o tej nazwie, stąd w przypadku komend wyżej tworzy `l3za`.
Z ciekawostek, volumeny można wyświetlić i usunąć
```bash=
docker volume ls
docker volume rm <img>
```
Jak naprawić?
Można sobie hasze sprawdzać i jeśli inny (to znaczy, że zmieniony), trzeba ręcznie usunąć i wtedy jak odpalimy po raz kolejny, to docker stworzy nowy i będzie śmigać
## Zadanie 4

### Sprawdzenie
Domyślnie systemy cgroups są montowane w katalogu `/sys/fs/cgroup`
Można sprawdzić np.
```bash=
$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
````
i wtedy w zależności od wersji mam cgroup1/2
### Sprawdzenie
Nie mam `procfs`, ale można też sprawdzić inaczej.
Cała historia
```bash=
$ ps
PID TTY TIME CMD
5519 pts/0 00:00:00 bash
$ cat /proc/5519/cgroup
0::/user.slice/user-1000.slice/user@1000.service/app.slice/app-org.gnome.Terminal.slice/vte-spawn-595ab7eb-d2ce-4ea2-bd73-f977199b9491.scope
```
I sprawdzenie ograniczeń to czytanie z plików /sys/fs/cgroups/<ścieżka z poprzedniego zadania>
Gdzie <ścieżka z poprzedniego zadania> to wszystsko po `::` z poprzedniego
```bash=
$ cd /sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/app.slice/app-org.gnome.Terminal.slice/vte-spawn-595ab7eb-d2ce-4ea2-bd73-f977199b9491.scope
$ cat memory.peak
```
### Utworzenie własnego cgroup
najpierw sprawdźmy czy w ogóle mamy kontrolery odpalone
```bash=
cat /sys/fs/cgroup/cgroup.subtree_control
```
Chcemy mieć
"cpu memory pids" jeśli nie mamy to
```bash=
echo "+cpu +memory +pids" > cgroup.subtree_control
```
Tworzenie grupy:
```bash=
mkdir moja_grupa
```
Ustawianie jej parametrów:
```bash=
echo "209715200" > moja_grupa/memory.max
echo "50000 100000" > moja_grupa/cpu.max
echo "10" > moja_grupa/pids.max
```
możemy stworzyć proces
```bash
sleep 3600 &
PID=<pid sleepa>
echo $PID > moja_grupa/cgroup.procs
```
Sanity check:
```bash
cat /proc/$PID/cgroup
```
weźmy na obraz cpu.stat
```bash
cat /sys/fs/cgroup/moja_grupa/cpu.stat
```
```
usage_usec 15023 # Całkowity czas CPU (w mikrosekundach) zużyty przez procesy w tej grupie.
user_usec 8010 # Czas CPU zużyty w trybie użytkownika.
system_usec 7013 # Czas CPU zużyty w trybie jądra (systemu).
nr_periods 0 # Liczba okresów (zgodnych z cpu.max), które minęły.
nr_throttled 0 # Ile razy procesy w tej grupie zostały "zdławione" (throttled), bo przekroczyły limit.
throttled_usec 0 # Całkowity czas (w mikrosekundach), przez który procesy były dławione.
```
## Zadanie 5

Kontroler to specjalny ziomek który monitoruje ile dane procesy zżerają zasobów i potrafi kontrolować te procesy (spowalniać je, zabijać)
Z ciekawszych ograniczeń to:
- `cpu.weight`, pozwala ustalić z jaką wagą dane procesy mają zabierać CPU (dwa procesy jeden 100 (33%), drugi waga 200 (66%))
- `memory.high`, jakieś która nie daje ci hard limitu tylko soft. Procesy nie są zabijane ale ich wydajność spada
https://man7.org/linux/man-pages/man7/cgroups.7.html
**Kontrolery** - inaczej subsystemy, modyfikują zachowanie procesów w danej **cgroup**ie
Ciekawe kontrolery:
<!-- 1. cpu - gwarantuje, ile CPU będzie dana cgroupa mogła używać (jeśli więcej jest "wolne" to może używać więcej)
3. cpuset - przypisuje procesy z cgroupy do konkretnych CPU
5. memory - pozwala na limitowanie użycia pamięci procesu, pamięci jądra i swapu -->
1. **devices** - umożliwia kontrolowanie, kto może tworzyć i otwierać na czytanie/pisanie różne urządzenia
* fajne bo można lepiej izolować procesy
2. **freezer** - pozwala na zatrzymywanie / wznawianie wszystkich procesów z cgroupy
* można czasowo zatrzymać jakieś procesy, np jeśli są istotnie obciążające i w danym momencie to bardzo przeszkadza
3. **net_cls** - dodaje **classid** do pakietów tworzonych przed procesy z danej cgroupy, może być to potem używane przez firewall
* np. wszystko z danej cgroupy musi zostać w podsieci i nie wychodzi na zewnątrz)
* można pakiety wychodzące z tej sgroupy poddać dodatkowej analizie -> bezpieczeństwo czy coś
* dotyczy tylko pakietów **wychodzących**, nie przychodzących
<!-- 10. blkio
11. perf_event -->
4. **net_prio** - pozwala na dodanie priorytetów dla cgroup do konkretnego interfejsu sieciowego
* dzięki temu, jeśli mamy cgroupę z procesami, które potrzebują jak najszybszy internet możemy je jako bardziej priorytetowe ustawić
* generalnie Quality of Service
<!-- 13. hugetlb
14. pids
15. rdma -->
### Różnice:
<!--  -->
Chodzi o to że w katalogu głównym mamy WSZYSTKIE kontrolery
Natomiast w podkatalogach (np ten user-100.slice) mamy ograniczony dostęp i tylko do tych mamy dostęp...
`cat /sys/fs/cgroup/cgroup.controllers`:
```
cpuset cpu io memory hugetlb pids rdma misc dmem
```
`cat /sys/fs/cgroup/user.slice/user-1000.slice/cgroup.controllers`:
```
cpu memory pids
```
Różnica wynika z tego, jakie kontrolery są dla kogo dostępne - zwykły użytkownik (user-1000) nie ma możliwości korzystania z kontrolerów cpuset, io, ...
Dlaczego tak a nie inaczej? Generalnie korzystanie z pozostałych mogłoby istotnie zaburzyć:
* pracę innych procesów (cpuset, io)
* to, do jakiej pamięci jest dostęp (hugetlb, rdma)
Generalnie nie chcemy użytkownikowi zwykłemu pozwalać na za duże kombinowanie.
## Zadanie 6

eee to inna wersja grupowania procesów tylko że przez systemd (? plox fact check me)
Jak utworzyć własny slice?
```bash
nano /etc/systemd/system/mojWaznySlice.slice
```
```toml
[Unit]
Description=Moj wlasny slice do ograniczania zasobow
# Ten slice nie musi byc uruchamiany recznie,
# wystartuje automatycznie, gdy pierwszy proces zostanie w nim umieszczony.
[Slice]
# Tu moglibysmy zagniezdzic go w innym slice, ale zostawiamy go w glownym.
[ResourceControl]
# Ograniczenie pamieci RAM do 500 Megabajtow
MemoryMax=500M
# Ograniczenie CPU do 30% mocy JEDNEGO rdzenia.
# (Jesli chcesz 30% CALKOWITEJ mocy np. 4 rdzeni, uzyj CPUWeight=...
# lub ustaw CPUQuota=120% dla 1.2 rdzenia)
CPUQuota=30%
```
Teraz tylko zostało odpalić dziada
```bash
systemctl daemon-reload
systemctl start mojWaznySlice.slice
```
Gemini ratuje i można tak odpalić ładnie procesy
```bash=
systemd-run --unit=proces1 --slice=mojWaznySlice.slice sleep 3600
systemd-run --unit=proces2 --slice=mojWaznySlice.slice top
```
`systemd-cgls` pokazuje strukturę drzewiastą obecnych cgroup i sliceów

`systemd-cgtop` htop ale na cgroupach

## Zadanie 7

pozdrawiam @Grzegorz dobry devops
### Całe demo
```bash
# Przygotowanie
docker run --rm -it --name test alpine /bin/sh
# W nowej karcie
PID=$(docker top test | awk 'NR>1 {print $2}')
CGROUP_PATH=$(cat /proc/$PID/cgroup | cut -d: -f3)
cat /sys/fs/cgroup/$CGROUP_PATH/cpu.weight
cat /sys/fs/cgroup/$CGROUP_PATH/memory.max
# Update
docker update --cpu-shares 512 --memory 10M --memory-swap 10M test
# Weryfikacja
cat /sys/fs/cgroup/$CGROUP_PATH/cpu.weight
cat /sys/fs/cgroup/$CGROUP_PATH/memory.max
# W kontenerze
dd if=/dev/zero of=/tmp/testfile bs=15M count=250
```
### Cgroup driver
Cgroup driver oznacza jak docker się komunikuje z systemem by zarządzać zasobami (albo to jest systemd albo cgroupfs)
### Twardy limit demo
Jeśli chodzi o twardy limit to można to zrobić tak.
```
docker run -it --rm --name dd-killer --memory=10m --memory-swap=10m alpine /bin/sh
```
i w środku odpalić
```
dd if=/dev/zero of=/tmp/testfile bs=15M count=250
```
### Twardy limit vs soft
Jak przekroczymy twardy to jest oom i zabijamy proces. Jeśli przekroczymy soft to po prostu tracimy priorytet i dużo na wydajności tracimy.
W jaki sposób są aplikowane? Na przykład hard w memory jest w `memory.max`, a soft jest w `memory.low`
### Limity pamięci
* `--memory-reservation` - miękki limit, kernel stara się go trzymać, ale nie zabija procesu, jeśli zostaje przekroczony
* `--memory` - twardy limit, jak przekroczymy to kernel zabija proces
Fun fact:
* kernel nie zawsze zabija proces, jeśli poprosimy o więcej niż przydzielona nam pamięć - potrafi obiecać ją nam i zabić proces dopiero, jak rzeczywiście tyle chcemy użyć?
* co to zmienia? nie tak łatwo zauważyć to zabijanie bo musimy serio wykorzystać tę pamięć nie tylko ją chcieć mieć
### W jaki sposób aplikowane są te limity?
systemd zapisuje odpowiednie rzeczy do cgroupów, kernel zajmuje się dbaniem o przestrzeganie tego
## Zadanie 8

https://docs.docker.com/build/bake/
Przykład:
Zamiast:
```bash
docker build -f Dockerfile -t myapp:latest .
```
Można:
docker-bake.hcl
```
target "myapp" {
context = "."
dockerfile = "Dockerfile"
tags = ["myapp:latest"]
}
```
```bash
docker buildx bake myapp
```
Przydatne jeśli do buildowania używamy dużo flag, generalnie jest to bardziej skomplikowane niż tylko nazwa czy coś, np:
```bash
docker build \
-f Dockerfile \
-t myapp:latest \
--build-arg foo=bar \
--no-cache \
--platform linux/amd64,linux/arm64 \
.
```
Bo bądźmy szczerzy - tego się nie zapamięta i łatwo się pomylić a tak to jest to gotowe i cyk wszystko pięknie kolorowo
Podsumowanie:
W skrócie docker compose jest to odpalania a docker bake jest do budowania
UWAGA! Wczesniej trzeba, zeby dzialalo multi-platform build:
```bash
docker buildx create --name multi --driver docker-container --use
docker buildx inspect --bootstrap
```
```bash
docker buildx bake app-dev --set app-dev.output=type=local,dest=./out
docker buildx bake app-prod --set app-prod.output=type=local,dest=./out2
```
Dockerfile:
```Dockerfile
FROM alpine:latest
# Przekaż argument czasu budowania do obrazu
ARG APP_VERSION="unknown"
ENV VERSION=$APP_VERSION
RUN echo "Budowanie obrazu w wersji: $VERSION" > /app-info.txt
RUN echo "To jest prosty obraz testowy." >> /app-info.txt
CMD ["cat", "/app-info.txt"]
```
docker-bake.hcl:
```hcl
# Definiujemy grupę "default".
# Jeśli wywołamy `docker buildx bake` bez podania celu,
# zbudowane zostaną wszystkie cele z tej grupy.
group "default" {
targets = ["app-dev"]
}
# --- CEL 1: Wersja deweloperska ---
# Prosty cel do budowania lokalnego
target "app-dev" {
# Kontekst budowania (katalog z Dockerfile)
context = "."
# Nazwa pliku Dockerfile
dockerfile = "Dockerfile"
# Tagi, jakie nadamy obrazowi
tags = ["my-simple-app:dev"]
# Argumenty budowania
args = {
APP_VERSION = "dev-0.1"
}
# Co zrobić z wynikiem: "local" ładuje obraz
# do lokalnego daemona Dockera.
output = ["type=local"]
}
# --- CEL 2: Wersja produkcyjna (Multi-platform) ---
# Cel do budowania na wiele architektur i wypchnięcia do rejestru
target "app-prod" {
# Dziedziczymy wszystko z "app-dev" (kontekst, dockerfile)
inherits = ["app-dev"]
# Nadpisujemy tagi
tags = ["my-registry/my-simple-app:1.0", "my-registry/my-simple-app:latest"]
# Nadpisujemy argumenty
args = {
APP_VERSION = "1.0.0-prod"
}
# Definiujemy platformy
platforms = ["linux/amd64", "linux/arm64"]
# Co zrobić z wynikiem: "registry" automatycznie
# wypycha obraz do rejestru (np. Docker Hub).
# UWAGA: To wymaga wcześniejszego `docker login my-registry`
output = ["type=registry"]
}
```
<!-- GEMINI wysrało to -->
<!-- ale to trzeba zcheckować -->