go to the english version

jak stworzyć obraz dockerowy z palca

Oto opis, jak stworzyć z palca bardzo prosty ale poprawny obraz dockerowy. Najpierw stworzę obraz pusty - więc nieuruchamialny, ale poza tym poprawny. Potem stworzę obraz dający się uruchomić.

pusty obraz

Najpierw tworzę tar z systemem plików - czyli tak zwaną warstwę. Ja chcę stworzyć obraz pusty (nie mający żadnych plików ani katalogów), więc tworzę pustego tara (sposobem ze stack exchange):

$ head --bytes=10240 /dev/zero > mylayer.tar

Potem sprawdzam, jaka jest suma kontrolna tego tara, bo będę ją zaraz potrzebował:

$ sha256sum mylayer.tar

Mi ta suma wyszła 84ff92691f909a05b224e1c56abb4864f01b4f8e3c854e4bb4c7baf1d3f6d652.

Potem piszę (w tym samym katalogu) dwa pliki konfiguracyjne. Najpierw plik z opisem warstwy, pod nazwą mylayer.json, o takiej treści:

{
    "id": "my_layer",
    "created": "2023-06-12T14:00:00Z",
    "container_config": {},
    "rootfs": {
        "type": "layers",
        "diff_ids": [
            "sha256:84ff92691f909a05b224e1c56abb4864f01b4f8e3c854e4bb4c7baf1d3f6d652"
        ]
    }
}

Widzisz, że w tym pliku użyłem tę sumę kontrolną, co przed chwilą sprawdzałem, tak?

Potem tworzę plik konfiguracyjny z opisem całego obrazu - który mógłby mieć wiele warstw, ale u mnie ma tylko jedną. To jest plik o nazwie manifest.json, jego treść to:

[
    {
        "Config": "mylayer.json",
        "Layers": ["mylayer.tar"]
    }
]

Razem zawartość mojego katalogu to:

$ ls
manifest.json  mylayer.json  mylayer.tar

Pakuję te trzy pliki tarem:

$ tar -c mylayer.tar manifest.json mylayer.json > ../image.tar

I takim to sposobem dostałem prosty, poprawny obraz dockerowy. Mogę go załadować do dockera poleceniem:

$ docker load < ../image.tar
84ff92691f90: Loading layer [==================================================>]  10.24kB/10.24kB
Loaded image ID: sha256:50300b2f83fc25768e1cf832278d6c9026b4d75ed73c87bbc8f5e4f0c2701318

To polecenie kopiuje rzeczy z tego obrazu do katalogu /var/lib/docker/image. Umieszcza je tam w swoim dziwnym formacie, którego nie rozumiem, więc nie licz, że jak tam zajdziesz, to po prostu znajdziesz tam wkopiowany swój plik image.tar.

Z polecenia docker load nie poszły żadne błędy, co dowodzi, że ten obraz jest poprawny. Wyświetlił się identyfikator, który docker nadał temu obrazowi (czyli napis 50300b2f83fc25768e1cf832278d6c9026b4d75ed73c87bbc8f5e4f0c2701318). Zapamiętaj go, będzie ci zaraz potrzebny.

Teraz możemy spróbować uruchomić ten obraz poleceniem:

$ docker run 50300b2f83fc25768e1cf832278d6c9026b4d75ed73c87bbc8f5e4f0c2701318
docker: Error response from daemon: No command specified.
See 'docker run --help'.

Nie zadziałało, co nie jest dziwne. Obraz nie ma w swoich plikach konfiguracyjnych podanego, jaki program ma być odpalony na jego starcie, nie podałem tego też przy uruchamianiu, no to docker mówi mi No command specified.

Nie bardzo mam co podawać, bo w mojej warstwie (w tym pustym tarze) nie mam żadnych plików wykonywalnych, ale mogę podać nieistniejącą ścieżkę, żeby chociaż zobaczyć, jak docker nie znajduje programu:

$ docker run 50300b2f83fc25768e1cf832278d6c9026b4d75ed73c87bbc8f5e4f0c2701318 /fiku/miku
WARNING: The requested image's platform (unknown) does not match the detected host platform (linux/amd64) and no specific platform was requested
docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "/fiku/miku": stat /fiku/miku: no such file or directory: unknown.
ERRO[0000] error waiting for container: context canceled 

Jak widać, docker nie znalazł programu fiku/miku.

To koniec sekcji o tworzeniu pustego obrazu. Omawiane pliki jakbyś chciał obejrzeć, to są tu.

obraz z prostą binarką

A teraz zrobię obraz, który będzie się dało nie tylko załadować, ale i uruchomić. Jego warstwa tym razem nie będzie pusta, ale będzie miała w sobie jeden wykonywalny plik - program wypisujący napis "Hello, world!".

Zacznę od zrobienia tego programu. Tworzę taki plik o nazwie hello.c:

#include 

int main() {
   printf("Hello, world!\n");
   return 0;
}

Kompiluję go statycznie (to ważne że statycznie - nie chcę, żeby ten program do działania potrzebował jakichś bibliotek) poleceniem:

$ gcc -static hello.c -o hello

Sprawdzam, czy program się uruchamia:

$ ./hello 
Hello, world!

Dobrze. To teraz stworzę tara, w którym będzie tylko ten jeden plik - mój plik wykonywalny hello (ten tar zaraz posłuży mi jako warstwa w moim obrazie):

$ tar cf mylayer.tar hello

Teraz będę tworzył, ładował i uruchamiał obraz tak samo jak poprzednio. Znaczy, najpierw sprawdzam sumę kontrolną warstwy:

$ sha256sum mylayer.tar

Mi ta suma wyszła 47ce307a2e16442b328bb821d69699007351ef6f26b278342c70cd799d272e1a Tobie może wyjść inna, choćby dlatego, że data tego pliku hello u ciebie będzie inna.

Potem piszę (w tym samym katalogu) dwa pliki konfiguracyjne. Najpierw plik z opisem warstwy, pod nazwą mylayer.json, o takiej treści:

{
    "id": "my_layer",
    "created": "2023-06-12T14:00:00Z",
    "container_config": {},
    "rootfs": {
        "type": "layers",
        "diff_ids": [
            "sha256:47ce307a2e16442b328bb821d69699007351ef6f26b278342c70cd799d272e1a"
        ]
    }
}

Widzisz, że w tym pliku użyłem tę sumę kontrolną, co przed chwilą sprawdzałem, tak?

Potem tworzę plik konfiguracyjny z opisem całego obrazu - który mógłby mieć wiele warstw, ale u mnie ma tylko jedną. To jest plik o nazwie manifest.json, jego treść to:

[
    {
        "Config": "mylayer.json",
        "Layers": ["mylayer.tar"]
    }
]

Razem zawartość mojego katalogu to:

$ ls
hello  hello.c	manifest.json  mylayer.json  mylayer.tar

Pakuję te trzy pliki - warstwę i dwa pliki konfiguracyjne - tarem:

$ tar -c mylayer.tar manifest.json mylayer.json > ../image.tar

I takim to sposobem dostałem obraz dockerowy. Mogę go załadować do dockera poleceniem:

$ docker load < ../image.tar
47ce307a2e16: Loading layer [==================================================>]  880.6kB/880.6kB
Loaded image ID: sha256:840877bd1e3faef46a6ea8699f5c0ef9cdf33fcae49a5e44274a25061841c50f

Z polecenia docker load nie poszły żadne błędy, co dowodzi, że ten obraz jest poprawny. Wyświetlił się identyfikator, który docker nadał temu obrazowi (czyli napis 840877bd1e3faef46a6ea8699f5c0ef9cdf33fcae49a5e44274a25061841c50f). Zapamiętaj go, będzie ci zaraz potrzebny.

Teraz możemy spróbować uruchomić ten obraz poleceniem:

$ docker run 840877bd1e3faef46a6ea8699f5c0ef9cdf33fcae49a5e44274a25061841c50f
docker: Error response from daemon: No command specified.
See 'docker run --help'.

Nie zadziałało, co nie jest dziwne. Obraz nie ma w swoich plikach konfiguracyjnych podanego, jaki program ma być odpalony na jego starcie, nie podałem tego też przy uruchamianiu, no to docker mówi mi No command specified. No to podam, żeby na starcie uruchomił program /hello (który, jak pamiętamy, jest w naszej warstwie):

$ docker run 840877bd1e3faef46a6ea8699f5c0ef9cdf33fcae49a5e44274a25061841c50f /hello
WARNING: The requested image's platform (unknown) does not match the detected host platform (linux/amd64) and no specific platform was requested
Hello, world!

Zadziałało.

To koniec sekcji o tworzeniu obrazu, który da się uruchomić. Omawiane pliki jakbyś chciał obejrzeć, to są tu.