2022年7月17日 星期日

Docker 筆記

Docker 架構







About Docker client

Docker client 實際上就是我們安裝好 Docker 後,提供給我們使用的 CLI command。他主要是負責透過Restful API 去呼叫 Docker daemon 來完成所需要的操作。

About Docker daemon

實際上是一個 dockerd 的服務程序,提供 Restful API (over gRPC) 給 Docker client進行操作,用來管理 Docker objects,包含 images, containers, networks 和 volumes。它也能夠用來和別的 Docker daemon 進行溝通來管理其他的 Docker services。

About containerd

containerd (docker-containerd)是一個 high-level 的 runtime 用來管理 container的整個生命週期。此外,還有一些額外的功能包含 下載(pulling) images, 建立 network interface,以及管理 low-level 的 runc的 instances。在啟動一個新的 container 的過程當中,containerd 並不是直接自己啟動一個新的 container。實際上,它是將需要的 Docker image 轉換成 OCI bundle,並且透過 runc 來使用這個 OCI bundle 來建立一個新的 container。當 container 這個 sub-process 啟動後,runc 就會離開。而 shim 會變成這個 container sub-process 的 parent process。

About shim

shim 是 container 的 parent process 用來負責維持 STDIN 和 STDOUT 的pipes是open的狀態, 因此當 Docker daemon 重啟時並不會造成 container 因為 pipes 被 close 後而被終止的情形。另外,shim 會負責回報目前 container的狀態給 Docker daemon。

About runc

TBD

Implementation on Linux


  • dockerd (the Docker daemon)
  • docker-containerd (containerd)
  • docker-containerd-shim (shim)
  • docker-runc (runc)

Containerd - https://github.com/containerd/containerd/releases

Docker images


Image Registries

用來存放 docker image 的位置,最常見的 registry 在 https://hub.docker.com/

可以用 docker info 指令來檢視  Registry:  所指定的位置
shell> docker info


Image Registries 可以有 1 個或個 多個 image repositories,而每個 repositories 可以有多個 images

Official repositories

Image Naming and Tagging

docker image pull <repository>:<tag>

以下指令是 從 top-level 的 repository 上面下載 tag 為 latest的 image

docker image pull alpine:latest
docker image pull redis:latest

請注意 tag 為 latest 並不代表一定就是最新的版本,具體要看那個 image 官方網站的版本

以下指令是 從 top-level 的 repository 的 namespace=tu-deom上面下載 tag 為 v2 的 image

docker image pull nigelpulton/tu-deom:v2

以下指令是透過 -a 參數,去指定下載全部的 tags 版本

docker image pull -a <repository>:<tag>

實際上 tag 只是一個任意的字串,具體的版號比對還是要看 IMAGE-ID


Images and Layers

在執行 docker image pull 時,會從 Layer-1 開始下載




利用下面這個指令可以檢視 image 的 layers

shell> docker image inspect ubuntu:latest

[ { "Id": "sha256:bd3d4369ae.......fa2645f5699037d7d8c6b415a10", "RepoTags": [ "ubuntu:latest" <Snip> "RootFS": { "Type": "layers", "Layers": [ "sha256:c8a75145fc...894129005e461a43875a094b93412", "sha256:c6f2b330b6...7214ed6aac305dd03f70b95cdc610", "sha256:055757a193...3a9565d78962c7f368d5ac5984998", "sha256:4837348061...12695f548406ea77feb5074e195e3", "sha256:0cad5e07ba...4bae4cfc66b376265e16c32a0aae9" ] } } ]

Pulling Images by Digest

由於 git tag 是可以修改的,或是說可以被 overwrite。因此,為了確保在做 pull image 時候確保下載的 docker image 是原本所預期的版本,我們可以透過以下指令來顯示該 image 的 DIGEST

shell> docker image ls --digests alpine
REPOSITORY   TAG       DIGEST                                                                    IMAGE ID       CREATED       SIZE
alpine       latest    sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c   e66264b98777   4 weeks ago   5.53MB


然後,利用以下命令來指定digest 做 docker image pull 
shell> docker image pull alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c

Multi-Architecture Images

Docker image 透過以下的結構來支援在單一個 docker image 同時支援不同平台架構(Linux on x64, Linux on PowerPC, Windows x64, Linux on different versions of ARM)

docker container run 時候,會先查找 Manifest List 中是否有對應支援的平台的 Manifest,如果有會先取得這個對應的 Manifest 然後才


檢視 manifest list 內容

shell> docker manifest inspect golang

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:ea66badd7cf7b734e2484a1905b6545bd944ef3bdeea18be833db3e2219f1153",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:d1faaf4c45064097d13d604c93724fa2b57f76223e31dc20091ee6aec5df65c6",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v5"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:2f6a38bfc8e43016a5f79d4d376e09bfd829566b20a40898798ba6a10b143269",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v7"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:47b9c8aa3f8b415aeca87a1e13353a611df4cf64a07e38f14b1bc9679d56a3b8",
         "platform": {
            "architecture": "arm64",
            "os": "linux",
            "variant": "v8"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:08c3c6149e409134d1773f215c8f207866605c1bec4bf6763175b5cc22eafb53",
         "platform": {
            "architecture": "386",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:9b853afe0ce8062bb0a1ca8babb72c0d5773bd14788f4471e2c9a7654745f01e",
         "platform": {
            "architecture": "mips64le",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:726e75aa04bb7ed012e7bf9984b0a1fe25e1e0316e0f9245cc68b2bd0af5112c",
         "platform": {
            "architecture": "ppc64le",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:9b1f50237df09f109696a52b6206d2d00692295bf675b649f8ed4fd6a9bcf31c",
         "platform": {
            "architecture": "s390x",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 3401,
         "digest": "sha256:cf9ffc87c29e1f1e72a74e714e58f00e55eed4c0acecf7fa5ea148cd46d8a5b2",
         "platform": {
            "architecture": "amd64",
            "os": "windows",
            "os.version": "10.0.20348.768"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 3401,
         "digest": "sha256:cbe8f5856e85ad134eec9e778abb3f4b50926e4a935afae9cace36adc3e6b58b",
         "platform": {
            "architecture": "amd64",
            "os": "windows",
            "os.version": "10.0.17763.3046"
         }
      }
   ]
}

How to build image support multi-arch 

export DOCKER_CLI_EXPERIMENTAL=enabled

docker buildx build --push --platform linux/arm/v7,linux/arm64/v8,linux/amd64 --tag your-username/multiarch-example:buildx-latest .

另一種方式去啟用 buildx 工具是去修改 config.json 如下
{ "experimental": true }

Reference:


Containers

一個 Image 可以用來建立多個 Containers 來執行。Containers 就是 Image 的一個 instance。

Inspecting docker container run

當執行這樣的指令時
shell> docker container run -it ubuntu:latest /bin/bash

實際上會做下面的事情:
  1. docker client 會將 command 的參數轉換後,呼叫 docker daemon 上面所提供的 API server (over gRPC)
  2. docker daemon 會接收到 command 的內容,並且查找 Docker host 的 local image repository是否有指定的 image
  3. 如果沒有,docker daemon 會去找 Docker Hub,如果有找到就會下載這個 image 存到 local image repository
  4. docker daemon 透過 containerd 和 runc 來用這個指定的 image 建立 container 並啟動

Stopping Containers Gracefully

應該要使用 docker container stop 來關閉 container,因為它會送出一個 SIGTERM 的訊號給正在執行的 main process。如果 main process 在10秒內沒有自己關閉,它將會收到一個 SIGKILL的訊號。

Self-Healing Containers with Restart Policies

  • always
  • unless-stopped
  • on-failed

shell> docker container run --name neversaydie -it --restart always alpine sh

shell> docker container run --name neversaydie -it --restart unless-stopped alpine sh

shell> docker container run --name neversaydie -it --restart on-failed alpine sh

unless-stopped

如果該 container 的狀態是 stopped 時,當重啟 docker daemon 時候,並不會自動啟動該 container

on-failure

這個 container 如果是因為收到 non-zero exit code 而離開時,它會自動被重啟


Containerizing an Application



  1. Start with your application code and dependencies.
  2. Create a Dockerfile that describes your app, its dependencies, and how to run it.
  3. Feed the Dockerfile into the docker image build command.
  4. Push the new image to a registry (optional).
    1. Docker login

    2. Docker tag <image_name> <your_docker_hub_username>/<image>:<version>

    3. Docker push <your_docker_hub_username>/<image>:<version>

  5. Run the container from the image.


Dockerfile

Dockerfile 檔名要完全大小寫一樣,以大寫D開頭,指令不分大小寫,但通常都是用大寫。
此外以下的指令都會建立一個新的 Layer
  • FROM
  • RUN
  • COPY




Containerize the Application

Building images

在提供 Dockerfile 的目錄執行以下指令來build一個新的 docker image 並且設定 tag 叫做 web:latest

shell> docker image build -t web:latest .

Tag the image

以下指令將 image 指定的tag,新增一個 tag

docker image tag <current-tag> <new-tag>

shell> docker image tag web:latest nigelpoulton/web:latest

Pushing images

shell> docker login --username {{username}} --password {{password}}
shell> docker image push nigelpoulton/web:latest

以上的指令,會是 push 到 Repository 為 nigelpoulton/web 的位置,而 image tag 為 latest

Build from Cache

docker image build 指令的處理過程會使用到 cache 的機制。它在處理 Dockerfile 時候,是根據每一個 layer 去檢查是否已經有存在的 layer 已經在 local cache 可以參考。因此,在撰寫 Dockerfile 時候,要盡量讓變動性大的 layer 放在越後面越好。如果不想要使用 cache 機制,在build docker image 時可以指定 --no-cache=true的參數。

Image history

檢視 image build 的歷史紀錄
shell> docker image history web:latest

Squash the Image

docker image build 指令加上參數 --squash 可以build出一個壓縮所有的 layers 變成只有一層 layer,因此這種方式是無法共享 layer的。


Docker 常用指令

檢查版本
shell> docker version

啟動docker daemon (MacOSX)
shell> open -a Docker

關閉 docker daemon (MacOSX)
shell> pkill -SIGUP -f  /Applications/Docker.app 'docker serve'

啟動docker daemon (Linux without systemd) 並檢查狀態
shell> sudo service docker start
shell> service docker status

啟動 docker daemon (Linux with systemd) 並檢查狀態
shell> sudo systemctl start docker
shell> systemctl  is-active docker


docker image 

Usage:  docker image COMMAND

Manage images


列出 images
shell> docker image list

列出 images 只有 tags 是 latest 的
shell> docker image list  --filter=reference="*:latest"

列出 images 用指定的格式輸出
shell> docker image list --format "{{.size}}" 
shell> docker image list --format "{{.Repository}}: {{.Tag}}: {{.Size}}"

下載 images
shell> docker image pull ubuntu:latest

Build image (在有Dockerfile的位置執行)
shell> docker image build -t image-name:latest . 

Build image 不使用 cache (在有Dockerfile的位置執行)
shell> docker image build --no-cache=true -t image-name:latest . 

Build image 不安裝 recommends 的 packages (如果Dockerfile有用到 apt-get install這種方式時)
shell> docker image build --no-install-recommends -t image-name:latest . 

從 Docker Hub 搜尋 repository name 指定 official 為 true 的限制 5 筆資料
shell> docker search  {repository-name} --filter "is-official=true" --limit 5 

從 Docker Hub 搜尋 repository name 指定 automated builds 為 true 的,
shell> docker search  {repository-name} --filter "is-automated=true"

刪除特定的 images
shell> docker image rm 02674b9cb179 02675b9cb180

刪除全部的 images
shell> docker image rm $(docker image ls -q) -f

docker container run (等同 docker service create)

Usage:  docker container run [OPTIONS]  IMAGE  [COMMAND]  [ARG...]

Run a command in a new container


啟動 container 互動模式, 執行指定的指令直到 /bin/bash 終止
shell> docker container run -it ubuntu:latest /bin/bash 

啟動 container 互動模式, 執行 sleep 10 秒, 然後關閉
shell> docker container run -it ubuntu:latest sleep 10 

啟動 container 為 daemon模式, 印出 container-id
shell> docker container run -d ubuntu:latest 


啟動 container 為 daemon模式, 並且 mount local 的 /Users/username/Desktop/ 目錄為 /desktop 的目錄,在 container 啟動後可以讀寫 /desktop的目錄
shell> docker run -it -v /Users/username/Desktop/:/desktop ubuntu:latest

啟動 container 為 daemon模式, 並且 mount local 的 /Users/username/Desktop/ 目錄為 /desktop 的目錄,在 container 啟動後只能唯讀 /desktop的目錄
shell> docker run -it -v /Users/username/Desktop/:/desktop:ro ubuntu:latest


啟動 container 為 daemon模式, 指定 container name 並且 publish container 8080 port 到 docker host 80 port
shell> docker container run -d  --name container-name --publish 8080:80  ubuntu:latest 

檢視正在執行的 containers
shell> docker container list  

檢視全部的 containers
shell> docker container list -a  

進入到 執行的container環境 
docker container exec <options> <container-name or container-id> <command/app>
shell> docker container exec -it ubuntu:latest /bin/bash 

Attach 到 container
shell> docker attach {container-id

停止 container
shell> docker stop {container-id or container-name

刪除 container
shell> docker rm {container-id or container-name} 

停止並刪除 container
shell> docker kill {container-id or container-name} 

docker logs

用來查找 指定的 application log
shell> docker logs -f {container-id} 

docker volume

建立 volume 名稱叫做 app_share_data
shell> docker volume app_share_data

列出 volume
shell> docker volume ls 

檢視特定的 volume 資訊
shell> docker volume inspect share_data 

docker system

檢視docker 已使用的空間大小
shell> docker system df 
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              36                  6                   12.19GB             11.48GB (94%)
Containers          18                  1                   28.31MB             28.06MB (99%)
Local Volumes       33                  0                   1.818GB             1.818GB (100%)
Build Cache         0                   0                   0B                  0B
而 total reclaimable 顯示有多少空間是可以回收的

回收 docker 所占用的空間
shell> docker system prune 

如果要回收特定的空間可以參考
  • docker container prune
  • docker images prune
  • docker network prune

docker network

列出 docker network interface 清單
shell> docker network list 

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
915fe26ffbc8        bridge              bridge              local
3ac2c0505d62        host                host                local
753c94184c7b        none                null                local
docker 內建支援三種 DRIVER:bridge, host, null,只有 bridge 是能夠設定的。

檢視 docker network interface
shell> docker inspect {network-id}


假設我們看到下面這兩個 container mysql webapp,我們想讓 webapp能夠存取 mysql。這時候,我們需要透過 docker run --link 的指令(--link name:alias)
shell> docker run --link "mysql:backenddb" webapp:latest 

當它執行成功後,我們可以在 webapp這個 container的 /etc/hosts 檔案中看到一筆 backenddb 的紀錄,它會有一個 docker 配置給 backenddb 的 IP address。

"Containers"{
            "029acceda0674c23a09c7f37b7c3af733cc295f429573e8ef266882d6fc3912a"{
                "Name""mysql",
                "EndpointID""f8184cbe809537ec80902323655c6f7ee9fd31940c601252f34fdede1d7201fd",
                "MacAddress""02:42:ac:11:00:03",
                "IPv4Address""172.17.0.3/16",
                "IPv6Address"""
            },
            "57c5fdd375b5e9e5f923fbc289623c3a12995559af501d9cab8be11e788893dc"{
                "Name""webapp",
                "EndpointID""af7da2a39e84006db2ecace93602acddaaa16b2e9603a6edf8c4d1a007c80e17",
                "MacAddress""02:42:ac:11:00:02",
                "IPv4Address""172.17.0.2/16",
                "IPv6Address"""
            }
        },


Configure Docker for TLS

會需要 以下的檔案

ca-key.pem << CA private key
ca.pem << CA public key (cert)
client-cert.pem << client public key (Cert)
client-key.pem << client private key
daemon-cert.pem << daemon public key (cert)
daemon-key.pem << daemon private key

Configuring the Docker daemon for TLS

修改 daemon.json (詳細規格)
MacOSX的 daemon.json 位置 /Users/user_name/.docker/daemon.json
{ "hosts": ["tcp://node3:2376"], "tls": true, "tlsverify": true, "tlscacert": "/home/ubuntu/.docker/ca.pem", "tlscert": "/home/ubuntu/.docker/cert.pem", "tlskey": "/home/ubuntu/.docker/key.pem" }

  • tlsverify enables TLS verification. 預設false,使用TLS並做後臺程序與客戶端通訊的驗證
  • tlscacert tells the daemon which CA to trust. 預設 ~/.docker/ca.pem,通過CA認證過的的certificate檔案路徑
  • tlscert tells Docker where the daemon’s certificate is located. 預設 ~/.docker/cert.pem ,TLS的certificate檔案路徑 
  • tlskey tells Docker where the daemon’s private key is located. 預設~/.docker/key.pem,TLS的key檔案路徑
  • hosts tells Docker which sockets to bind the daemon on.

Warning! Linux systems running systemd don’t allow you to use the “hosts” option in daemon.json. Instead, you have to specify it in a systemd override file. You may be able to do this with the sudo systemctl edit docker command. This will open a new file called /etc/systemd/system/docker.service.d/override.conf in an editor. Add the following three lines and save the file.

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://node3:2376


檢視 指定的 host 的 docker daemon 版本 
shell> docker -H tcp://node3:2376 version


Configuring the Docker client for TLS

export DOCKER_HOST=tcp://node3:2376
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=位置指向 ca.pem, cert,pem, 和 key.pem的目錄
docker version



沒有留言:

張貼留言

歡迎留言討論與指教