Programowanie Aplikacji w Chmurze Obliczeniowej
dr inż. Sławomir Wojciech Przyłucki
Termin zajęć:
wtorek, godz. 14:15,
Numer na liście:
9,
Imię i nazwisko:
Paweł Pieczykolan,
III rok studiów inżynierskich, IO 6.7.
1. Informacje ogólne
2. Część obowiązkowa
» 2.1 Przygotowanie wymaganych plikówy
» 2.2 Zbudowanie obrazu
» 2.3 Inspekcja obrazu
» 2.4 Uruchomienie kontenera
» 2.5 Weryfikacja działania
3. Część dodatkowa
» 3.1 Modyfikacja pliku Dockerfile
» 3.2 Utworzenie i użycie sterownika Docker Buildx
» 3.3 Zbudowanie obrazu z użyciem Docker Buildx
» 3.4 Inspekcja obrazu
» 3.5 Uruchomienie kontenera
» 3.6 Weryfikacja działania
» 3.7 Porównanie obrazów
4. Podsumowanie
Podstawowe informacje dotyczące opracowania
W części obowiązkowej należało stworzyć serwer w dowolnym języku programowania, który działa w kontenerze « Docker » i realizuje określoną funkcjonalność: po uruchomieniu kontenera, serwer zapisuje w logach datę uruchomienia, imię i nazwisko autora (studenta) oraz port « TCP », na którym nasłuchuje na zgłoszenia klienta. Na podstawie adresu « IP » klienta, serwer wyświetla stronę informującą o adresie « IP » klienta oraz o dacie i godzinie w jego strefie czasowej. Należało opracować plik Dockerfile, który umożliwia zbudowanie obrazu kontenera z serwerem, z optymalizacją wieloetapowego budowania obrazu, użycia warstwy scratch, optymalizacji cache, zawartości i ilości warstw. Plik « Dockerfile » miał zawierać informację o autorze.
Aplikacja serwerowa, która wyświetla stronę internetową « HTML », została napisana w języku « C ». Wykorzystano obrazy « Alpine » w wersji 3.19.1 oraz « Scratch » w celu drastycznego zmniejszenia objętości obrazu (> 1 MB).
Część dodatkowa wymagała zbudowania obrazu kontenera z aplikacją opracowaną w części obowiązkowej, które działałyby na architekturach: linux/arm64 oraz linux/amd64, wykorzystując sterownik « Docker Container ». « Dockerfile » powinien wykorzystywać rozszerzony frontend, zawierać deklaracje wykorzystania cache i umożliwiać bezpośrednie wykorzystanie kodów aplikacji umieszczonych w publicznym repozytorium na GitHub. Opracowane obrazy należało przesłać do swojego repozytorium na DockerHub.
W sprawozdaniu należało podać wykorzystane instrukcje wraz z wynikiem ich działania oraz ewentualnymi komentarzami, unikając przekazywania danych wrażliwych. Wszystkie informacje wymagane w sprawozdaniu z części nieobowiązkowej należało opracować w postaci pliku zadanie1_dod.md. W repozytorium należało umieścić opracowane źródła dla serwera, przygotowany plik « Dockerfile » oraz inne niezbędne pliki. Jako sprawozdanie należało przekazać wyłącznie plik tekstowy zawierający linki do użytego repozytorium na GitHub oraz DockerHub.
Note
W realizacji zadania napisania serwera w dowolnym języku, wykorzystano informacje pochodzące ze strony dev.to.
Zadanie wykonano na maszynie wirtualnej VM VirtualBox, na systemie Ubuntu w wersji 20.04 LTS.
Przygotowanie plików serwerowych, zbudowanie obrazu, uruchomienie kontenera oraz weryfikacja działania serwera
Utworzono plik « Dockerfile » spełniający wymogi zadania.
#️⃣ Etap 1: Budowanie pliku binarnego serwera C
FROM alpine:3.19.1 AS builder
#️⃣ Ustawienie zmiennej środowiskowej BASE_VERSION, z domyślną wartościa v1
ARG BASE_VERSION
ENV APP_VERSION=${BASE_VERSION:-v1}
#️⃣ Instalacja kompilatora C i musl-dev
RUN apk add --no-cache build-base musl-dev \
&& rm -rf /var/cache/apk/*
#️⃣ Ustawienie katalogu roboczego
WORKDIR /app
#️⃣ Kopiowanie pliku źródłowego
COPY server.c .
#️⃣ Kompilacja statyczna, utworzenie pliku binarnego
RUN gcc -static -o server server.c
#️⃣ Kopiowanie pliku index.html
COPY index.html .
#️⃣ Etap 2: Tworzenie minimalnego obrazu
FROM scratch as final
#️⃣ Ustawienie zmiennej środowiskowej BASE_VERSION, z domyślną wartościa v1
ARG BASE_VERSION
ENV APP_VERSION=${BASE_VERSION:-v1}
#️⃣ Informacje o autorze, dokumentacji i licencji
LABEL org.opencontainers.image.authors="Paweł Pieczykolan"
LABEL org.opencontainers.image.documentation="https://github.com/97703/DockerLabZad1"
LABEL org.opencontainers.image.licenses="GNU v3.0"
#️⃣ Kopiowanie bin z etapu budowania
COPY --from=builder /app/server /server
#️⃣ Kopiowanie pliku index.html
COPY --from=builder /app/index.html /index.html
#️⃣ Ustawienie informacji o porcie
EXPOSE 8080
#️⃣ Komenda startowa
ENTRYPOINT ["/server"]Rys. 1. Plik Dockerfile
Utworzono prosty plik serwera w języku « C » server.c oraz stronę internetową « HTML » index.html korzystającą ze skryptu « Javascript ».
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Informacje o Kliencie</title>
<script>
#️⃣ Wywołanie API do pobrania adresu IP
fetch("https://api.ipify.org?format=json")
#️⃣ Konwersja odpowiedzi na format JSON
.then(response => response.json())
#️⃣ Obsługa danych
.then(data => {
#️⃣ Wyświetlenie adresu IP
document.getElementById("ip").innerText = data.ip;
#️⃣ Pobranie danych dotyczących czasu związanych ze strefą czasową ze strony worldtimeapi
return fetch("https://worldtimeapi.org/api/ip/" + data.ip);
})
#️⃣ Konwersja odpowiedzi na format JSON
.then(response => response.json())
#️⃣ Obsługa danych
.then(data => {
#️⃣ Utworzenie obiektu czasu
var czas = new Date(data.utc_datetime);
#️⃣ Wyświetlenie czasu
document.getElementById("czas").innerText = czas.toLocaleString();
#️⃣ Wyświetlenie strefy czasowej
document.getElementById("strefa").innerText = data.timezone;
})
#️⃣ Obsługa błędów
.catch(error => console.error('Błąd:', error));
#️⃣ Zamknięcie sekcji skryptu
</script>
</head>
<body>
<h1>Informacje o Kliencie</h1>
<p>Adres IP klienta: <span id="ip"></span></p>
<p>Czas związany ze strefą czasową tego IP: <span id="czas"></span></p>
<p>Strefa czasowa: <span id="strefa"></span></p>
</body>
</html>
Rys. 2. Plik index.html, analiza skryptu Javascript (komentarze)
Zbudowano obraz korzystając z pliku « Dockerfile ».
docker build --build-arg BASE_VERSION=v1.0 -t local/dockerzad1:v1
Rys. 3. Zbudowanie obrazu dockerzad1:v1
Wyświetlono liczbę warstw nowoutworzonego obrazu.
docker history local/dockerzad1:v1
Rys. 4. Warstwy obrazu dockerzad1:v1
Sprawdzono obraz pod względem liczebności i jakości potencjalnych luk bezpieczeństwa.
docker scout quickview local/dockerzad1:v1
Rys. 5. Liczba i jakość luk bezpieczeństwa w obrazie dockerzad1:v1
Wyświetlono również jego objętość.
docker images --filter "reference=local/dockerzad1"
Rys. 6. Objętość obrazu części obowiązkowej
Uruchomiono kontener na podstawie nowoutworzonego obrazu.
docker run -p 8080:8080 --name dockerzad1 local/dockerzad1:v1
Rys. 7. Uruchomienie kontenera dockerzad1
Sprawdzono dziennik zdarzeń za pomocą
docker logs dockerzad1
Rys. 8. Dziennik zdarzeń dla kontenera dockerzad1 (fragment)
Uruchomiono przeglądarkę « Firefox » i sprawdzono działanie serwera.
Rys. 9. Efekt działania kontenera
Zmodyfikowanie pliku Dockerfile, użycie Docker Buildx oraz Docker Scout
Zmodyfikowano plik « Dockerfile » z części nieobowiązkowej.
Dodano:
# syntax=docker/dockerfile:1.4– odwołanie do oficjalnej i stabilnej wersji obrazu dla rozszerzonych frontend-ów- do instrukcji « RUN »
apk add --no-cache openssh-client git \– instalacja klienta « OpenSSH » oraz « Git » w kontenerze « Docker » - do instrukcji « RUN »
mkdir -p -m 0700 ~/.ssh– utworzenie foldera « .ssh » w katalogu domowym z uprawnieniami 700 (pełne uprawnienia dla właściciela katalogu), o ile taki katalog nie istnieje - do instrukcji « RUN »
ssh-keyscan github.com >> ~/.ssh/known_hosts && eval $(ssh-agent)– konfiguracja środowiska do korzystania z GitHuba poprzez SSH; uruchomienie agenta SSH RUN --mount=type=ssh git clone git@github.com:97703/DockerZad1.git DockerZad1– klonowanie prywatnego repozytorium « Git », przy wykorzystaniu bezpiecznego dostępu « SSH »mv /app/DockerZad1/server.c /app/DockerZad1/index.html /app– przeniesienie plików ze sklonowanego repozytorium- do pierwszej instrukcji « RUN » etapu pierwszego
--mount=type=cache,target=/var/cache/apk– wskazuje folder « apk » dla mechanizmu cache'owania - do drugiej instrukcji « RUN » etapu pierwszego
--mount=type=cache,target=/var/cache/gcc– wskazuje folder « gcc » dla mechanizmu cache'owania
+# syntax=docker/dockerfile:1.4
#️⃣ Etap 1: Budowanie pliku binarnego serwera C
FROM alpine:3.19.1 AS builder
#️⃣ Ustawienie zmiennej środowiskowej BASE_VERSION, z domyślną wartościa v1
ARG BASE_VERSION
ENV APP_VERSION=${BASE_VERSION:-v1}
#️⃣ Instalacja kompilatora C i musl-dev
+RUN --mount=type=cache,target=/var/cache/apk \
apk add --no-cache build-base musl-dev \
+ #️⃣ Czyszczenie pamięci podręcznej apk
&& rm -rf /var/cache/apk/* \
+ #️⃣ Instalacja klienta SSH i GITa
+ && apk add --no-cache openssh-client git \
+ #️⃣ Konfiguracja SSH dla dostępu do GitHub
+ && mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts && eval $(ssh-agent)
#️⃣ Ustawienie katalogu roboczego
WORKDIR /app
-#️⃣ Kopiowanie pliku źródłowego
-COPY server.c .
-#️⃣ Kompilacja statyczna, utworzenie pliku binarnego
-RUN gcc -static -o server server.c
-#️⃣ Kopiowanie pliku index.html
-COPY index.html .
+#️⃣ Sklonowanie repozytorium i przeniesienie plików
+RUN --mount=type=ssh git clone git@github.com:97703/DockerZad1.git DockerZad1 \
+ && mv /app/DockerZad1/server.c /app/DockerZad1/index.html /app
+#️⃣ Kompilacja statyczna, utworzenie pliku binarnego
+RUN --mount=type=cache,target=/var/cache/gcc \
+ gcc -static -o server server.c
#️⃣ Etap 2: Tworzenie minimalnego obrazu
FROM scratch as final
#️⃣ Ustawienie zmiennej środowiskowej BASE_VERSION, z domyślną wartościa v1
ARG BASE_VERSION
ENV APP_VERSION=${BASE_VERSION:-v1}
#️⃣ Informacje o autorze, dokumentacji i licencji
LABEL org.opencontainers.image.authors="Paweł Pieczykolan"
LABEL org.opencontainers.image.documentation="https://github.com/97703/DockerLabZad1"
LABEL org.opencontainers.image.licenses="GNU v3.0"
#️⃣ Kopiowanie bin z etapu budowania
COPY --from=builder /app/server /server
#️⃣ Kopiowanie pliku index.html
COPY --from=builder /app/index.html /index.html
#️⃣ Ustawienie informacji o porcie
EXPOSE 8080
#️⃣ Komenda startowa
ENTRYPOINT ["/server"]
Rys. 10. Zmiany w pliku Dockerfile
Utworzono nowy sterownik Docker Buildx o nazwie dockerzad1.
docker buildx create --name dockerzad1 --driver docker-container --bootstrap
Nowoutworzony sterownik ustawiono jako domyślny za pomocą
docker buildx use dockerzad1
Rys. 11. Utworzenie i konfiguracja sterownika Docker Container
Zbudowano obraz w oparciu o polecenie Docker Buildx
docker buildx build -f Dockerfile-2 --sbom=true --provenance=mode=max --ssh default=$SSH_AUTH_SOCK --build-arg BASE_VERSION=v.1.0 --platform linux/arm64,linux/amd64 --cache-from=type=registry,ref=docker.io/ppieczykolan/dockerzad1-2:cache --cache-to=type=registry,ref=docker.io/ppieczykolan/dockerzad1-2:cache -t ppieczykolan/dockerzad1-2 --push .
, gdzie:
docker buildx build– polecenie służace do budowania obrazów przy użyciu BuildKit-f Dockerfile-2– określa nazwę pliku « Dockerfile »--sbom=true– generowanie zestawienia materiałów oprogramowania (spisu komponentów oprogramowania użytych w celu zbudowania danej aplikacji)provenance=mode=max– określa poziom szczegółowości rejestrowania etapów kompilacjissh default=$SSH_AUTH_SOCK– umożliwia procesowi kompilacji korzystanie z uwierzytelnienia « SSH »--build-arg BASE_VERSION=v.1.0– przekazuje argument wersji do pliku « Dockerfile »--platform linux/arm64,linux/amd64– określa platformy docelowe; platformy dla których obraz jest budowany--cache-from=type=registry,ref=docker.io/ppieczykolan/dockerzad1-2:cache– określa źródło z którego można pobrać pamięć podręczną « cache »--cache-to=type=registry,ref=docker.io/ppieczykolan/dockerzad1-2:cache– określa miejsce docelowe pamięci podręcznej « cache »; miejsce udostępnienia pamięci « cache »-t ppieczykolan/dockerzad1-2– określa miejsce docelowe obrazu (reporyztorium DockerHub lub dysk)--push– nakazuje przesłanie obrazu do repozytorium.– określa kontekst kompliacji
Rys. 12. Zbudowanie obrazu ze zmodyfikowanego pliku Dockerfile – ½
Rys. 13. Zbudowanie obrazu ze zmodyfikowanego pliku Dockerfile – ²⁄₂
Warning
Błąd
ERROR importing cache manifest from docker.io/ppieczykolan/dockerzad1-2:cache
nie jest istotny, gdyż jest to pierwsze budowanie obrazu – nie możemy pobrać « cache » z serwera ponieważ nie został on nigdy wcześniej udostępniony.
Zezwolono na analizowanie obrazu przez usługę Docker Scout
Rys. 14. Podłączenie usługi Docker Scout pod obraz dockerzad1-2
Zweryfikowano zbudowany obraz pod kątem samej budowy.
docker buildx imagetools inspect ppieczykolan/dockerzad1-2
Rys. 15. Sprawdzenie obecności i liczebności luk bezpieczeństwa
Sprawdzono liczbę warstw dla nowego obrazu.
docker history ppieczykolan/dockerzad1-2
Rys. 16. Warstwy obrazu części dodatkowej
Sprawdzono również obecność luk bezpieczeństwa.
docker scout quickview ppieczykolan/dockerzad1-2
Rys. 17. Sprawdzenie obecności i liczebności luk bezpieczeństwa
Obraz jest dostępny pod tym adresem.
Zaciągnięto obraz ze zdalnego repozytorium za pomocą
docker pull ppieczykolan/dockerzad1-2
Rys. 18. Zaciągnięcie obrazu dockerzad1-2
Uruchomiono kontener na podstawie zaciągniętego obrazu.
docker run -p 8080:8080 --name dockerzad1-2 ppieczykolan/dockerzad1-2:latest
Rys. 19. Uruchomienie kontenera części dodatkowej
Zweryfikowano działanie kontenera części dodatkowej. Uruchomiono przeglądarkę i wpisano w pasku adresowym « http://localhost:8080 ».
Rys. 20. Efekt działania kontenera części dodatkowej
Porównano utworzone w tym rozwiązaniu obrazy pod względem objętości.
docker images
Rys. 21. Porównanie obrazów
Wnioski
Zadanie zostało zrealizowane pomyślnie.
W części obowiązkowej opracowano plik « Dockerfile » oraz przygotowano prosty serwer w języku « C », który działa w kontenerze « Docker ». Serwer ten wyświetla informacje o adresie « IP » klienta oraz o dacie i godzinie w jego strefie czasowej.
W części dodatkowej zbudowano obraz kontenera z aplikacją opracowaną wcześniej, który działa na różnych architekturach. Ponadto zastosowano rozszerzony frontend, mechanizm cache'owania oraz udostępniono obraz z cachem na DockerHub.
Wszystkie cele zadania zostały osiągnięte. Dzięki wykorzystaniu narzędzi takich jak Docker Buildx i Docker Scout, możliwe było zautomatyzowanie, ulepszenie procesu budowy obrazów i analiza obrazów pod kątem bezpieczeństwa, a zastosowanie warstwy Scratch w drugim etapie budowy pozwoliło na zmniejszenie objętości pliku do 315kB bez potencjalnych luk bezpieczeństwa.


















