## Zadanie 1 ![Screenshot from 2025-10-16 21-01-57](https://hackmd.io/_uploads/SJxMz606lx.png) Przygotowanie środowiska: HOST: ```bash= # pobieramy apt-cacher-ng i go konfigurujemy sudo apt install apt-cacher-ng sudo nano /etc/apt-cacher-ng/acng.conf # Chcemy zmienić następujące rzeczy (głównie to odkomentować): # - VerboseLog: 1 # - AllowUserPort: 80 443 3142 # - BindAddress: 0.0.0.0 ``` Może jeszcze warto tylko sprawdzić jakie ip się ma ```bash= ip a ``` ![image](https://hackmd.io/_uploads/Hy1m0nJCge.png) No i w teorii to tyle :DD KLIENT: To tak ja sobie postawiłem obraz który dostaliśmy na VirtualBoxie, tutaj jest konfiguracja sieci jaką zrobiłem. ![image](https://hackmd.io/_uploads/ByKAT3yRxx.png) Jak coś wszystko jest robione na koncie roota (login: root, hasło: root) po odpaleniu oczywiście trzeba ogarnąć sieć ```bash= ip link set up dev enp0s3 ip addr add <jakaś sieć z podsieci twojego ip> dev enp0s3 # sanity check ping <ip_hosta> ``` i ja tutaj trochę robie okropny trick póki co (bo nie umiem po bożemu) ```bash= nano /etc/apt/sources.list # i powinna tam być 1 linijka # https://deb.debian.org/(...) # podmieniamy ją na # http://<ip_hosta>:3142/deb.debian.org/(...) ``` TODO: `etc/apt/apt.conf.d/01proxy` -> do apt.conf.d możemy wrzucać pliki jako patche na podstawową konfigurację, numeracja 2 cyfry (wedlug kolejnosci sa nakladane) Ogl Nowak powiedział, że nasze też git -> jak kto woli po tym wszystkim robimy ```bash= apt update apt install firefox-esr apt remove --purge firefox-esr apt clean cache apt install firefox-esr (powinniśmy zobaczyć że jest ładnie fetch) ``` HOST: szybki factcheck że wszystko działa (i sprawdzić że mieliśmy hity) ```bash= ls -la /var/log/apt-cacher-ng/ # powinniśmy zobaczyć że zwiększa się rozmiar pliku apt-cacher.log # Najlepiej to w ogóle odpalić tail -f /var/log/apt-cacher-ng/apt-cacher.log # inna możliwość jak np pobierzemy jakiś pakiet to sprawdzenie czy jest w cache'u find /var/cache/apt-cacher-ng -name "*<nazwa_paczki>*.deb" ``` TODO: może zrobić to po bożemu ## Zadanie 2 ![Screenshot from 2025-10-16 21-02-12](https://hackmd.io/_uploads/rkxGzpR6lx.png) Dockerfile: ```dockerfile FROM ubuntu:22.04 # Instalacja SSH i innych niezbędnych pakietów RUN apt-get update && apt-get install -y \ openssh-server \ sudo ## To bym wywalił, bo chyba nie potrzebujemy? Z definicji raczej się nie dodaje sudo && mkdir /var/run/sshd # Ja bym to przeniósł do osobnego RUN # Konfiguracja SSH RUN echo 'root:password' | chpasswd RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config # Dodanie klucza publicznego RUN mkdir -p /root/.ssh COPY id_rsa.pub /root/.ssh/authorized_keys RUN chmod 600 /root/.ssh/authorized_keys # Ekspozycja portu SSH EXPOSE 22 # Uruchomienie serwera SSH CMD ["/usr/sbin/sshd", "-D"] ``` ^ Uwaga do uwagi Grzesia -> to sudo chyba jest potrzebne, bo inaczej trzeba zrobic RUN chmod + odpowiednie uprawnienia do tych folderów **Generowanie kluczy SSH:** ```bash ssh-keygen -t rsa -b 4096 -f id_rsa -N "" ``` **Budowanie obrazu:** ```bash docker build -t ssh-server . ``` **Uruchomienie kontenera:** ```bash docker run -d -p 2222:22 --name ssh-container ssh-server ``` **Testowanie połączenia:** ```bash ssh -i id_rsa -p 2222 root@localhost ``` **Testowanie trybu interaktywnego:** ```bash docker run -it ssh-container /bin/bash # W kontenerze uruchom: /usr/sbin/sshd -D ``` To mi nie działa, bo budowaliśmy ssh-server, więc u mnie śmiga tylko ```bash docker run -it ssh-server /bin/bash ``` +1 dla grzesia **Sprawdzenie statusu kontenera:** ```bash docker ps docker ps --all ``` **Odpowiedzi na pytania:** * kontener po wyjściu z trybu interaktywnego zostaje zatrzymany, ale nie usunięty * `docker ps` pokazuje tylko działające kontenery * `docker ps --all` pokazuje wszystkie ## Zadanie 3 ![Screenshot from 2025-10-16 21-02-34](https://hackmd.io/_uploads/HJxkGfTApxx.png) `Multi-stage build` w Dockerze to technika budowania obrazów, która pozwala na **oddzielenie środowiska kompilacji od środowiska wykonawczego**, co skutkuje **mniejszymi**, **bezpieczniejszymi** i **bardziej wydajnymi** obrazami końcowymi. Pozwala to na uniknięcie dodawania zbędnych artefaktów kompilacji, takich jak narzędzia developerskie, do finalnego obrazu. Jak to działa: * W jednym pliku `Dockerfile` można użyć wielu instrukcji `FROM`, które definiują poszczególne etapy budowy. * Dane (np. skompilowany kod) są kopiowane z jednego etapu (np. etapu kompilacji) do innego (np. etapu produkcyjnego). * Finalny obraz jest tworzony tylko z ostatniego etapu, który zawiera tylko niezbędne do uruchomienia aplikacji pliki i zależności, co znacząco zmniejsza jego rozmiar. Korzyści: * **Mniejsze obrazy:** Eliminacja zbędnych narzędzi kompilacji i artefaktów znacząco redukuje rozmiar obrazu. * **Większe bezpieczeństwo:** Mniejsze obrazy zawierają mniej potencjalnych luk w zabezpieczeniach. * **Lepsza wydajność:** Mniejsze obrazy szybciej się pobierają i uruchamiają. * **Łatwiejsze utrzymanie:** Proces budowy jest bardziej zorganizowany i łatwiejszy do zrozumienia. [Obszerny artykuł o multistage build](https://programistajava.pl/2025/01/01/co-to-jest-docker-multistage-build-i-jak-z-niego-korzystac/) [Docsy](https://docs.docker.com/build/building/multi-stage/) Izolowane środowisko budowania Włączenie BuildKit: ```bash export DOCKER_BUILDKIT=1 ``` Użycie Docker Buildx: ```bash # Sprawdzenie dostępnych builderów docker buildx ls # Tworzenie nowego buildera docker buildx create --name mybuilder --use ``` Przykładowy Dockerfile z izolacją: ```dockerfile # syntax=docker/dockerfile:1 FROM ubuntu:22.04 AS build WORKDIR /app COPY . . RUN make build FROM ubuntu:22.04 AS runtime COPY --from=build /app/bin /usr/local/bin/ CMD ["/usr/local/bin/myapp"] ``` SKOWI VERSION <- Better:DD Ale po co nam w ogóle ten multistage? No to taki ficzur, który pozwala wiele razy korzystać z `FROM`. Pewnie ktoś mógłby zapytać, "no dobra, ale po co mi to?". Otóż to pozwala tworzyć mniejsze obrazy. Tak jak w przykładzie poniżej, możemy zacząć od gcc:13 i wykonać jakieś ciężkie obliczenia (kompilacja cpp), a później przekowiować już gotową apkę dalej (już do czegoś co korzysta np. z lekkiego debiana). Gemini mówi coś co chyba ma sens ![image](https://hackmd.io/_uploads/SJKtEWUAll.png) ```dockerfile FROM gcc:13 AS builder WORKDIR /app COPY main.cpp . RUN g++ -static -o app main.cpp FROM debian:bookworm-slim WORKDIR /app COPY --from=builder /app/app . CMD ["/app/app"] ``` main.cpp ```c++ #include <iostream> int main() { std::cout << "Hello from Docker multistage build!" << std::endl; return 0; } ``` Budowanie z izolacją: ```bash docker buildx build --platform linux/amd64 -t my-app . --load ``` Runowanie: ```bash docker run -it my-app ``` TODO: ktoś miał `copy <<EOF .... EOF` -> spoko rzecz ## Zadanie 4 ![Screenshot from 2025-10-20 15-30-21](https://hackmd.io/_uploads/Hy99K2mRgg.png) https://docs.docker.com/build/building/best-practices/ ```dockerfile FROM ubuntu:22.04 RUN apt-get update RUN apt-get install -y curl FROM alpine ENV ADMIN_USER=mark RUN echo $ADMIN_USER > ./mark RUN unset ADMIN_USER ``` Problemy i nieoczekiwane zachowania: * Wielokrotne instrukcje `RUN`: Każdy `RUN` tworzy nową warstwę, co zwiększa rozmiar obrazu. * Przełączanie bazowych obrazów: Ubuntu jest zastępowany przez Alpine (tracimy `curl`'a z Ubuntu) * Zmienne środowiskowe: unset `ADMIN_USER` nie usuwa zmiennej z poprzednich warstw. * Brak optymalizacji cache warstw: `apt-get update` i `apt-get install` powinny być w jednej instrukcji. * Ze względu bezpieczeństwa -> przy installu możemy korzystać ze spamiętanego stanu `apt-get update`, więc będziemy korzystać z cache, zamiast wymusić update. (niezaktualizowane paczki + stare repozytoria) Błędnie poprawiona wersja: ```dockerfile FROM ubuntu:22.04 RUN apt-get update && \ apt-get install -y curl # Usuwa ubuntu (z curlem) # FROM alpine:latest # Jak używamy ENV, to tworzy się wartstwa, do której potem możemy się podłączyć ENV ADMIN_USER=mark # Wszystkie operacje w jednej warstwie RUN echo $ADMIN_USER > ./mark && \ unset ADMIN_USER ``` Poprawiona wersja: ```dockerfile FROM ubuntu:22.04 RUN apt-get update && \ apt-get install -y curl # Usuwa ubuntu (z curlem) # FROM alpine:latest # Wszystkie operacje w jednej warstwie (BEZ ENVA) RUN export ADMIN_USER="mark" && \ echo $ADMIN_USER > ./mark && \ unset ADMIN_USER ``` TODO: Można ustawić na puste, można używać arg, ## Zadanie 5 ![Screenshot from 2025-10-20 15-30-03](https://hackmd.io/_uploads/S19qtnQAle.png) <!-- Przygotowanie kluczy na hoście: ```bash # Generowanie klucza prywatnego openssl genrsa -out ca-key.pem 4096 # Generowanie certyfikatu CA openssl req -new -x509 -days 365 -key ca-key.pem -out ca-cert.pem # Generowanie klucza serwera openssl genrsa -out server-key.pem 4096 # Generowanie żądania certyfikatu serwera openssl req -new -key server-key.pem -out server.csr # Podpisanie certyfikatu serwera przez CA openssl x509 -req -days 365 -in server.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem ``` Dockerfile z bezpiecznym kopiowaniem certyfikatów: ```dockerfile FROM nginx:alpine # Tworzenie katalogu na certyfikaty RUN mkdir -p /etc/nginx/ssl # Kopiowanie certyfikatów (bez klucza prywatnego CA!) COPY server-cert.pem /etc/nginx/ssl/ COPY server-key.pem /etc/nginx/ssl/ # Konfiguracja NGINX z SSL COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 443 CMD ["nginx", "-g", "daemon off;"] ``` Konfiguracja NGINX (nginx.conf): ```nginx events {} http { server { listen 443 ssl; ssl_certificate /etc/nginx/ssl/server-cert.pem; ssl_certificate_key /etc/nginx/ssl/server-key.pem; location / { return 200 "Hello Secure World!"; } } } JANOOOO ODJEBAŁEŚ, to wyżej nie jest zgodne z treścią xD ``` --> Setup must have: ``` sudo apt install docker-buildx ``` Najpierw na hoście ```bash= openssl genrsa -out ca-key.pem 4096 openssl req -new -x509 -days 365 -key ca-key.pem -out ca-cert.pem -subj "/C=PL/ST=OK/L=WROCLAW/O=UWUR/OU=AHA/CN=X" ``` Dockerek :DD ```dockerfile= FROM ubuntu:latest RUN apt-get update && \ apt-get install -y openssl RUN --mount=type=secret,id=ca_key \ --mount=type=secret,id=ca_cert \ openssl genrsa -out cert-key.pem 4096 && \ openssl req -new -key cert-key.pem -out cert.csr -subj "/C=CZ/ST=/L=CZEWA/O=SKOWI_CORP/OU=/CN=SKOWI_CEO" && \ openssl x509 -req -days 365 -in cert.csr -CA /run/secrets/ca_cert -CAkey /run/secrets/ca_key -CAcreateserial -out signed-cert.pem ``` Tutaj warto podkreślić, że mamy ten `--mount=type=secret` przez co jest bezpiecznie:D https://docs.docker.com/build/building/secrets/ build i run ```bash= docker build -t zad5 --secret id=ca_key,src=./ca-key.pem --secret id=ca_cert,src=./ca-cert.pem . docker run -it zad5 ``` By sprawdzić certyfikat w kontenerze możemy odpalić ```bash= openssl x509 -in signed-cert.pem -text -noout ``` ## Zadanie 6 ![Screenshot from 2025-10-20 15-29-54](https://hackmd.io/_uploads/r1gcqFhQ0el.png) ### `--cpuset-cpus` Kiedy używać: Gdy chcemy przypisać kontener do konkretnych procesorów CPU. Uwaga od Nowaka: Np jeśli mamy kartę sieciową, to chcemy mieć blisko, żeby nie przechodzić między maszynami (cache są połączone, dokładniej L3 i to jets wolne) Przykład użycia: ```bash # Uruchomienie kontenera na CPU 0 i 1 docker run -d --cpuset-cpus="0,1" --name limited-cpu nginx # Ograniczenie do zakresu CPU docker run -d --cpuset-cpus="0-3" --name range-cpu nginx ``` ### `docker image ls --all`: ```bash # Pokazuje wszystkie obrazy (również pośrednie) docker image ls --all ``` ### `docker image history`: ```bash # Pokazuje historię budowania obrazu docker image history nazwa_obrazu # Z wyjściem w formacie JSON docker image history --format json nazwa_obrazu ``` ### `docker image inspect`: ```bash # Szczegółowe informacje o obrazie docker image inspect nazwa_obrazu # Wyciągnięcie konkretnych informacji docker image inspect --format='{{.Config.Cmd}}' nazwa_obrazu ``` ## Zadanie 7 ![Screenshot from 2025-10-20 15-29-39](https://hackmd.io/_uploads/ryxcqK2XCle.png) Problem: Skrypt smok.sh nie ma praw wykonywania i ścieżka w CMD jest nieprawidłowa. Poprawiony Dockerfile: ```dockerfile FROM ubuntu:latest # Tworzenie użytkownika RUN groupadd -r smok && useradd --no-log-init -r -g smok smok # Kopiowanie skryptu COPY smok.sh /home/smok/smok.sh # Nadanie praw wykonywania RUN chmod +x /home/smok/smok.sh # Zmiana właściciela pliku RUN chown smok:smok /home/smok/smok.sh # Przełączenie na użytkownika smok USER smok # Uruchomienie skryptu z pełną ścieżką CMD ["/home/smok/smok.sh"] ``` Poprawiony skrypt smok.sh: ```bash #!/bin/bash for i in {1..10}; do echo $i sleep 0.5 done ``` Testowanie: ```bash # Budowanie obrazu docker build -t fixed-smok . # Uruchomienie docker run --rm fixed-smok ``` można też tak z WORKDIRem <- ogólnie warto korzystać z tego `WORKDIR`, zdaje się, że to dobra praktyka:D ```dockerfile (...) WORKDIR /home/smok RUN chmod +x ./smok.sh USER smok CMD ./smok.sh ``` To ja pokażę merge obu, ta końcówka to dobra praktyka, tak samo jak `WORKDIR` ```dockerfile= FROM ubuntu:latest # Tworzenie użytkownika RUN groupadd -r smok && useradd --no-log-init -r -g smok smok # Kopiowanie skryptu COPY smok.sh /home/smok/smok.sh # Zmiana folderu WORKDIR /home/smok # Dodanie uprawnień RUN chmod +x ./smok.sh USER smok CMD ["./smok.sh"] ``` ## Zadanie 8 ![Screenshot from 2025-10-20 15-29-21](https://hackmd.io/_uploads/r159YhXRlg.png) Problematyczny fragment: ```dockerfile RUN dd if=/dev/zero of=largefile bs=1M count=100 RUN rm largefile ``` Problem: Każda instrukcja `RUN` tworzy warstwę. Plik jest usuwany w kolejnej warstwie, ale nadal istnieje w poprzedniej. Rozwiązania: Łączenie instrukcji: ```dockerfile RUN dd if=/dev/zero of=largefile bs=1M count=100 && \ rm largefile ``` Multi-stage build: ```dockerfile FROM ubuntu:latest AS builder RUN dd if=/dev/zero of=largefile bs=1M count=100 # Operacje na dużym pliku FROM ubuntu:latest COPY --from=builder /app/result /app/result # Tylko potrzebne pliki są kopiowane ``` Użycie .dockerignore: ``` largefile *.tmp ``` Minimalne obrazy bazowe: ```dockerfile FROM alpine:latest # zamiast ubuntu # Alpine jest znacznie mniejszy ``` Czyszczenie cache w tej samej warstwie: ```dockerfile RUN apt-get update && \ apt-get install -y package && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* ``` Sprawdzenie rozmiaru: ```bash docker images docker history nazwa_obrazu ``` Docker squash (plugin, teraz w dockerze, ale ma być z powrotem przeniesiony do pluginów) -> patrzy sobie na to co wszystkie wartstwy pozmieniały w systemie plików i squashuje je do jednej warstwy z tym co jest istotne (czyli wyrzuci rzeczy odpowiedzialne za stworzenie dużego niepotrzebnego pliku a potem go usunięcie) --- INNE Włączenie BuildKit: ```bash export DOCKER_BUILDKIT=1 # Lub w pliku ~/.docker/config.json ``` Przykładowy Dockerfile z zaawansowanymi funkcjami: ```dockerfile # syntax=docker/dockerfile:1 FROM alpine:latest AS builder RUN apk add --no-cache git make WORKDIR /src COPY . . RUN make build FROM alpine:latest WORKDIR /app COPY --from=builder /src/bin/app . CMD ["./app"] ``` Budowanie z BuildKit: ```bash docker build -t buildkit-example . # Z zaawansowanymi opcjami docker build \ --progress=plain \ --no-cache \ -t buildkit-example . ``` Dlaczego używać BuildKit? - Lepsza wydajność, cache, bezpieczeństwo, multi-stage build.