---
# System prepended metadata

title: Devops 3

---

## Zadanie 1

![Screenshot from 2025-10-18 21-17-14](https://hackmd.io/_uploads/SJEX_PWCxl.png)

### 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
![Screenshot from 2025-10-18 21-17-23](https://hackmd.io/_uploads/HJN7_PZAxx.png)

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
![Screenshot from 2025-10-18 21-17-33](https://hackmd.io/_uploads/ByxVXOvbRgx.png)

### 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
![Screenshot from 2025-10-18 21-17-46](https://hackmd.io/_uploads/ByE7OPZ0eg.png)

### 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


![Screenshot from 2025-10-18 21-17-55](https://hackmd.io/_uploads/BkNQdDWCle.png)

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:

<!-- ![Screenshot_20251022_234211](https://hackmd.io/_uploads/rJSa1AURex.png) -->
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


![Screenshot from 2025-10-18 21-18-07](https://hackmd.io/_uploads/S1EX_PZAee.png)

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
![Screenshot_20251022_235312](https://hackmd.io/_uploads/HJHUM08Rel.png)


`systemd-cgtop` htop ale na cgroupach
![Screenshot_20251022_235136](https://hackmd.io/_uploads/SJOeGC8Rxg.png)


## Zadanie 7

![Screenshot from 2025-10-18 21-18-14](https://hackmd.io/_uploads/rkE7uDZ0gx.png)
    
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

![Screenshot from 2025-10-18 21-18-23](https://hackmd.io/_uploads/BJ77OvZ0xg.png)

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ć -->