twtrubiks/docker-tutorial

Docker 基本教學 - 從無到有 Docker-Beginners-Guide 教你用 Docker 建立 Django + PostgreSQL 📝

django
django-rest-framework
docker
postgresql
tutorial

docker-tutorial

Docker 基本教學 - 從無到有 Docker-Beginners-Guide

教你用 Docker 建立 Django + PostgreSQL 📝

其他說明

簡介

Docker

Containers as a Service ( CaaS ) - 容器如同服務

算是近幾年才開始紅的技術,蠻多公司都有使用 Docker,而且真的很方便,值得大家去了解一下 :smile:

如果你有環境上不統一的問題? 請用 Docker :smile:

如果你有每次建立環境都快抓狂的問題? 請用 Docker :blush:

如果你想要高效率、輕量、秒開的環境,請用 Docker :blush:

如果你不想搞死自己,請用 Docker :smile:

如果你想潮到出水,請一定要用 Docker :laughing:

什麼是 Docker

Docker 是一個開源專案,出現於 2013 年初,最初是 Dotcloud 公司內部的 Side-Project。

它基於 Google 公司推出的 Go 語言實作。( Dotcloud 公司後來改名為 Docker )

技術原理我們這邊就不提了,簡單提一下他的好處。

我們先來看看官網的說明

Comparing Containers and Virtual Machines ( 傳統的虛擬化 )

從這張圖可以看出 Containers 並沒有 OS ,容量自然就小,而且啟動速度神快

詳細可參考 https://www.docker.com/what-container

Virtual Machines 是什麼?

類似 https://www.virtualbox.org/,我們可能用它裝裝看其他作業系統,例如說

我是 MAC,但我想玩 Windows,我就會在 MAC 中裝 VM 並且灌 Windows 系統。

一個表格了解 Docker 有多棒 :+1:

Feauture  Containers Virtual Machines ( 傳統的虛擬化 )
啟動   秒開 最快也要分鐘
容量 MB GB
效能
支援數量 非常多 Containers 10多個就很了不起了
複製相同環境 超慢

不理解:question::question::question:

我們來看一張圖,包準你懂

圖的來源 https://blog.jayway.com/2015/03/21/a-not-very-short-introduction-to-docker/

為什麼要使用 Docker

潮~ 不解釋 :satisfied:

更有效率的利用資源

比起像是 https://www.virtualbox.org/,Docker 的利用率更高,我們可以設定更多

的 Containers ,而且啟動速度飛快!!:flushed:

統一環境

相信大家都有每次搞電腦的環境都搞的很煩的經驗 :angry:

假設今天公司來了個新同事,就又要幫他建立一次環境 :expressionless:

不然就是,我的電腦 run 起來正常阿~ 你的怎麼不行,是不是 xxx 版本的關係阿 :joy:

相信大家多多少少都遇過上面這些事情,我們可以透過 Docker 來解決這些問題,

保持大家環境一致,而且要建立的時候也很快 :smile:

對於 DevOps 的好處

對於 DevOps 來說,最希望的就是可以設定一次,將來在其他地方都可以快速建立環境且正常執行。

Docker 概念

建議大家先了解一下 Docker 中的幾個名詞,分別為

Image

映像檔,可以把它想成是以前我們在玩 VM 的 Guest OS( 安裝在虛擬機上的作業系統 )。

Image 是唯讀( R\O )

Container

容器,利用映像檔( Image )所創造出來的,一個 Image 可以創造出多個不同的 Container,

Container 也可以被啟動、開始、停止、刪除,並且互相分離。

Container 在啟動的時候會建立一層在最外(上)層並且是讀寫模式( R\W )。

這張圖解釋了 Image 是唯讀( R\O )以及 Container 是讀寫模式( R\W ) 的關係

更多關係可參考 https://docs.docker.com/engine/userguide/storagedriver/imagesandcontainers/#images-and-layers

Registry

可以把它想成類似 GitHub,裡面存放了非常多的 Image ,可在 Docker Hub 中查看。

更詳細的我這邊就不再解釋惹,留給大家做作功課:stuck_out_tongue:

安裝

Windows

請先到 Docker 官網

https://www.docker.com/docker-windows

下載 stable 版本

接下來就是無腦安裝,安裝完後他會叫你登出電腦,點下去後就會幫你登出電腦

接著如果你的電腦沒有啟用 Hyper-V ,他會叫你重啟電腦 (一樣,點下去就對惹)

( 更多可 Hyper-V 介紹請參考 https://docs.microsoft.com/zh-tw/virtualization/hyper-v-on-windows/about/ )

重新開機後,你就會發現可愛的 Docker 在右下角蹦出來惹

我們可以再用 cmd 確認一下是否有成功安裝

docker --version
docker-compose --version

記得再設定一個東西 Shared Drives

裝完了之後,建議大家再多裝一個 Kitematic,他是 GUI 介面的,方便你使用 Docker,

( 後面會再介紹一個更贊的 GUI 介面 portainer :grin: )

我知道打指令很潮,但還是建議裝一下。

直接對著你的 Docker 圖示右鍵,就可以看到 Kitematic

下載回來直接解壓縮雙點擊即可使用

MAC

MAC 我本身也有,但因為更早之前就裝了,步驟就沒記錄了,基本上大同小異

https://www.docker.com/docker-mac

Linux

Youtube Tutorial-Ubuntu(Linux) 如何安裝 docker

這裡使用 Ubuntu 當作範例,

雖然在 ubuntu 中有 snap 可以非常快速的安裝 docker,

但在這邊我們不使用 snap 的方法安裝:smile:

請參考官方文件步驟安裝,

Get Docker Engine - Community for Ubuntu

Get Docker Engine - Community for Ubuntu

安裝後步驟 (optional:exclamation:), 但建議參考一下

Post-installation steps for Linux

docker-compose 的安裝

docker-compose install

系統資源分配問題,

假如你是使用 windows 或是 mac 的 docker,

你會有一個界面可以設定你要分多少的 cpu 以及 ram 給你的 docker,

通常會在 Preferences -> Advanced, 有 GUI 界面,

但如果是使用 linux, 就不會有這個界面, 因為在 Linux 中,

會自動依照系統的資源進行分配.

指令介紹

接著介紹一些 Docker 的指令,

Docker 的指令真的很多,這裡就介紹我比較常用的或是實用的指令

查看目前 images

docker images

建立 image

docker create [OPTIONS] IMAGE [COMMAND] [ARG...]

詳細的參數可參考 https://docs.docker.com/engine/reference/commandline/create/

範例 ( 建立一個名稱為 busybox 的 image )

docker create -it --name busybox busybox

刪除 Image

docker rmi [OPTIONS] IMAGE [IMAGE...]

查看目前運行的 container

docker ps

查看目前全部的 container( 包含停止狀態的 container )

docker ps -a

新建並啟動 Container

docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]

舉個例子

docker run -d -p 80:80 --name my_image nginx

-d 代表在 Detached( 背景 )執行,如不加 -d,預設會 foreground ( 前景 ) 執行

-p 代表將本機的 80 port 的所有流量轉發到 container 中的 80 port

--name 設定 container 的名稱

在舉一個例子

 docker run -it --rm busybox

--rm 代表當 exit container 時,會自動移除 container。 ( incompatible with -d )

更詳細的可參考 https://docs.docker.com/engine/reference/run/

啟動 Container

docker start [OPTIONS] CONTAINER [CONTAINER...]

如果想讓他在前景跑順便觀看輸出 , 可以使用以下指令

docker start -a [OPTIONS] CONTAINER [CONTAINER...]

--attach-a 代表 Attach STDOUT/STDERR and forward signals.

更詳細的可參考 https://docs.docker.com/engine/reference/commandline/start/

( container ID 寫幾個就可以了,和 Git 的概念是一樣的 ,

不了解 Git 可以參考 Git-Tutorials GIT基本使用教學

停止 Container

docker stop [OPTIONS] CONTAINER [CONTAINER...]

重新啟動 Container

docker restart [OPTIONS] CONTAINER [CONTAINER...]

删除 Container

docker rm [OPTIONS] CONTAINER [CONTAINER...]

--volumes , -v 加上這個參數,會移除掉連接到這個 container 的 volume。

可參考 https://docs.docker.com/engine/reference/commandline/rm/

進入 Container

docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
docker exec -it <Container ID> bash

使用 root 使用者進入 

docker exec -u 0 -it <Container ID> bash
docker exec -u root -it <Container ID> bash

打指令比較潮,或是說你也可以透過 Kitematic 進入。

當我們進入了 Container 之後,有時候想看一下裡面 Linux 的版本,

這時候可以使用以下指令查看

cat /etc/os-release

查看 Container 詳細資料

docker inspect [OPTIONS] NAME|ID [NAME|ID...]

查看 log

docker logs [OPTIONS] CONTAINER

--follow , -f , Follow log output

更詳細的可參考 https://docs.docker.com/engine/reference/commandline/logs/

從最後 100 行開始追蹤,

docker logs -f --tail 100 CONTAINER

或是

docker logs -f -n 100 CONTAINER

可以透過 --since 從指定時間到現在的 log,

例如,

docker logs --since 2023-04-13T09:20:00 <container_id>

從 10 分鐘前到現在的 log

docker logs --since 10m CONTAINER

1 小時前到現在的 log

docker logs --since 1h CONTAINER

如果想指定時間, 查看特定區間的 logs,

可以先使用 -t 找出 docker 的時間格式,

docker logs -t CONTAINER

接著就可以使用 --since--until 指定時間段,

一定要用 docker 的時間格式, 不然會無法生效.

這邊來個組合, 找出 8:10 ~ 8:30 的全部 log

docker logs --since 2023-12-10T8:10:00.346748975Z  --until 2023-12-10T8:30:00.346748975Z  CONTAINER

也可以把 log 寫進去檔案中,

docker logs CONTAINER >> access.log

如果上述指令沒有生效, 請修改成以下

docker logs CONTAINER >& access.log

也可以先過濾 log 再寫進檔案中,

docker logs CONTAINER | grep "29/Mar/2022" >> access_tmp.log

如果上述指令沒有生效, 請修改成以下

docker logs CONTAINER 2>&1 | grep "29/Mar/2022" >& access_tmp.log

顯示容器資源 ( CPU , I/O ...... )

docker stats [OPTIONS] [CONTAINER...]

也可以加上 --no-stream

docker stats --no-stream

--no-stream Disable streaming stats and only pull the first result.

注意:exclamation::exclamation:這邊得到的 memory usage 會比實際上的還要小,

因為這邊的值是再減去 cache usage memory.

相關 issues 可參考 https://github.com/moby/moby/issues/32253

On Linux, the Docker CLI reports memory usage by subtracting cache usage from the total memory usage.

詳細說明可參考 https://docs.docker.com/engine/reference/commandline/stats/

也可參考 https://docs.docker.com/config/containers/runmetrics/

查看 container 中正在執行的 processes

docker top CONTAINER

停止指定的 CONTAINER 中全部的 processes

docker pause CONTAINER [CONTAINER...]

執行 docker pause 之後,可以試這用 docker ps 查看,會發現

還是有在執行,這裡拿 docker stop 比較一下,差異如下。

docker stop : process 級別。

docker pause: container 級別。

恢復指定暫停的 CONTAINER 中全部的 processes

docker unpause CONTAINER [CONTAINER...]

docker tag

docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]

範例

docker tag 0e5574283393 twtrubiks/nginx:version1.0

更多可參考 https://docs.docker.com/engine/reference/commandline/tag/

儲存 (備份) image 成 tar 檔案

[OPTIONS] IMAGE [IMAGE...]

範例

docker save busybox > busybox.tar

docker save --output busybox.tar busybox

或 ( 也可以一次備份多個 )

docker save -o images.tar postgres:9.6 busybox

更多可參考 https://docs.docker.com/engine/reference/commandline/save/

載入 image

docker load [OPTIONS]

範例

docker load < busybox.tar

docker load -i busybox.tar

更多可參考 https://docs.docker.com/engine/reference/commandline/load/

顯示 image 的 history,查詢 image 的每一層

docker history [OPTIONS] IMAGE

在 docker 中,一層一層的概念很重要。

更多可參考 https://docs.docker.com/engine/reference/commandline/history/

剛剛有教大家如何儲存 (備份) images, 載入 images,

還有另外一種是 export 和 import containers,

docker export container 請參考 https://docs.docker.com/engine/reference/commandline/export/

docker import container 請參考 https://docs.docker.com/engine/reference/commandline/import/

其他指令

刪除所有 dangling images

docker image prune

移除全部 unused images (不只 dangling images)

docker image prune -a

更多資訊可參考 image_prune

停止所有正在運行的 Container

docker container stop $(docker ps -q)

更多資訊可參考 container_stop

移除全部停止的 containers

docker container prune

更多資訊可參考 container_prune

ENTRYPOINT

教學說明請點選 entrypoint-tutorial

Volume

接下來要介紹 Volume,Volume 是 Docker 最推薦存放 persisting data( 數據 )的機制,

使用 Volume 有下列優點

  • Volumes are easier to back up or migrate than bind mounts.
  • You can manage volumes using Docker CLI commands or the Docker API.
  • Volumes work on both Linux and Windows containers.
  • Volumes can be more safely shared among multiple containers.
  • Volume drivers allow you to store volumes on remote hosts or cloud providers, to encrypt the contents of volumes, or to add other functionality.
  • A new volume's contents can be pre-populated by a container.

在 container 的可寫層中,使用 volume 是一個比較好的選擇,因為他不會增加 container 的容量,

volume 的內容存在於 container 之外。

也可參考下圖

更詳細的可參考 https://docs.docker.com/engine/admin/volumes/volumes/

查看目前的 volume

docker volume ls [OPTIONS]

創造一個 volume

docker volume create [OPTIONS] [VOLUME]

刪除一個 volume

docker volume rm [OPTIONS] VOLUME [VOLUME...]

查看 volume 詳細資料

docker volume inspect [OPTIONS] VOLUME [VOLUME...]

移除全部未使用的 volume

docker volume prune [OPTIONS]

也可以建立 readonly 的 volumes (容器內 readonly)

docker-compose.yml 方法如下,

version: '3.5'
services:
  nginx:
    image: nginx
    ports:
      - "80:80"
    volumes:
      - "nfs-data:/data:ro,z"

volumes:
    nfs-data:

如果要可讀寫, 就設定 rw.

volumes 在容器內的確不能寫 (只能讀)

alt tag

使用以下的指令查看 Mounts, 觀察它的 Mode

docker inspect <container ID>

alt tag

也可以建立 NFS volumes,

docker-compose.yml 方法如下,

version: '3.5'
services:
  nginx:
    image: nginx
    ports:
      - "80:80"
    volumes:
      - "nfs-data:/data"

volumes:
    nfs-data:
      driver: local
      driver_opts:
        type: nfs
        o: nfsvers=4,addr=ip,rw
        device: ":/path/to/dir"

可以用以下指令查看設定

docker volume ls
docker inspect <volume name>

alt tag

NFS 相關文章可參考 linux-nfs-server - 如何在 ubuntu 啟用 NFS Server

network

建議大家花點時間研究 docker 中的 network,會蠻有幫助的 :smiley:

查看目前 docker 的網路清單

docker network ls [OPTIONS]

詳細可參考 https://docs.docker.com/engine/userguide/networking/

docker 中的網路主要有三種 Bridge、Host、None,預設皆為 Bridge 模式。

指定 network 範例 ( 指定使用 host 網路 )

docker run -it --name busybox --rm --network=host busybox

建立 network

docker network create [OPTIONS] NETWORK

移除 network

docker network rm NETWORK [NETWORK...]

移除全部未使用的 network

docker network prune [OPTIONS]

查看 network 詳細資料

docker network inspect [OPTIONS] NETWORK [NETWORK...]

將 container 連接 network

docker network connect [OPTIONS] NETWORK CONTAINER

更多詳細資料可參考 https://docs.docker.com/engine/reference/commandline/network_connect/

Disconnect container network

docker network disconnect [OPTIONS] NETWORK CONTAINER

更多詳細資料可參考 https://docs.docker.com/engine/reference/commandline/network_disconnect/

User-defined networks

這個方法是官方推薦的 :thumbsup:

透過內建的 DNS 伺服器,可以直接使用容器名稱,就可解析出 IP,不需要再使用 IP 讓容器互相

溝通,我們只需要知道容器的名稱就可以連接到容器。

更多詳細資料可參考 https://docs.docker.com/engine/userguide/networking/#user-defined-networks

docker-compose

再來要介紹 docker-compose,可參考官網 https://docs.docker.com/compose/

Compose 是定義和執行多 Container 管理的工具,不懂我在說什麼:question::question::question:

試著想想看,通常一個 Web 都還會有 DB,甚至可能還有 RedisCelery

所以說我們需要有 Compose 來管理這些,透過 docker-compose.yml ( YML 格式 ) 文件。

docker-compose.yml 的寫法可參考 https://docs.docker.com/compose/compose-file/

也可以直接參考官網範例 https://docs.docker.com/compose/compose-file/#compose-file-structure-and-examples

Compose 的 Command-line 很多和 Docker 都是類似的,

可參考 https://docs.docker.com/glossary/?term=compose

查看目前 Container

docker-compose ps

加上 -q 的話,只顯示 id

docker-compose ps -q

啟動 Service 的 Container

docker-compose start  [SERVICE...]

停止 Service 的 Container ( 不會刪除 Container )

docker-compose stop [options] [SERVICE...]

重啟 Service 的 Container

docker-compose restart [options] [SERVICE...]

Builds, (re)creates, starts, and attaches to containers for a service

docker-compose up [options] [--scale SERVICE=NUM...] [SERVICE...]

加個 -d,會在背景啟動,一般建議正式環境下使用。

docker-compose up -d

然後如果你有很多個 docker-compose.yml docker-compose-dev.yml,

你可以透過 -f 決定你要執行哪一個, 範例如下,

docker-compose -f ./docker-compose-dev.yml up -d

-f --file FILE Specify an alternate compose file

(default: docker-compose.yml)

up 這個功能很強大,建議可以參考 https://docs.docker.com/compose/reference/up/

如果你希望每次都重新 build image,可以加上

--build ( Build images before starting containers. )

docker-compose up -d --build

docker-compose down

docker-compose down [options]

down 這個功能也建議可以參考 https://docs.docker.com/compose/reference/down/

舉個例子

docker-compose down -v

加個 -v 就會順便幫你把 volume 移除( 移除你在 docker-compose.yml 裡面設定的 volume )

在指定的 Service 中執行一個指令

docker-compose run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]
[ARGS...]

舉個例子

docker-compose run web bash

在 web Service 中執行 bash 指令

可參考 https://docs.docker.com/compose/reference/run/

觀看 Service logs

docker-compose logs [options] [SERVICE...]

檢查 docker-compose.yml 格式是否正確

docker-compose config

如下指令,和 docker exec 一樣

docker-compose exec

範例 ( 進入 web 這個 service 的 bash )

docker-compose exec web bash

顯示被使用到的 container 中的 images 清單

docker-compose images

移除 service containers

docker-compose rm

Pushes images 到 docker hub

docker-compose push

目前這個指令其實我也搞不太懂,可參考 https://github.com/docker/compose/issues/4283

官網也解釋的沒有很清楚 https://docs.docker.com/compose/reference/push/

docker-compose up/down 和 restart 的差異

先來談 docker-compose up/down,

假如今天你修改了 docker-compose.yml 又或是更新了 image,

當你要重建 docker , 有幾種方法,

方法一.

先停止 container, 執行 docker-compose down 再執行 docker-compose up.

方法二.

不需要停止 container, 直接執行 docker-compose up -d.

(他會自動幫你重建, 很方便, 不需要多一步先關閉 container )

結論, 只要你的 docker-compose.yml 有任何變動, 一定要執行 docker-compose up 才會生效.

再來談 docker-compose restart,

請看官方文件 docker-compose restart, 如果你對 docker-compose.yml 修改, 然後使用這個指令, 是不會生效的,

但是, 如果你是改 code (可能是 python code), 那這個指令是有效的.

docker-compose networks

這邊多補充 docker-compose networks 的觀念,因為剛好最近有用到:smile:

version: '3.5'
services:

    db:
      container_name: 'postgres'
      image: postgres
      environment:
        POSTGRES_PASSWORD: password123
      ports:
        - "5432:5432"
        # (HOST:CONTAINER)
      volumes:
        - pgdata:/var/lib/postgresql/data/
      networks:
        - proxy

    web:
      build: ./api
      command: python manage.py runserver 0.0.0.0:8000
      restart: always
      volumes:
        - api_data:/docker_api
        # (HOST:CONTAINER)
      ports:
        - "8000:8000"
        # (HOST:CONTAINER)
      depends_on:
        - db
      networks:
        - proxy

volumes:
    api_data:
    pgdata:

networks:
    proxy:
      # external:
        name: my_network

先把 version 改成 3.5,因為這版本才開始有 networks name 的概念,在

db 以及 web 中都加了 networks ( 自己定義的 ),定義的地方在最後面,

proxy 是名稱 ( 類似 volumes 的概念 ),external option 的意思代表

是不是要參考外部別人已經定義好的 network ( 所以如果找不到就會報錯 ),

但如果不加上 external option,也就代表是自己定義的,會幫你自動建立

你所定義的 network,名稱為 my_network。

如果你都完全沒有定義 networks,預設就是資料夾的名稱_default 。

docker-compose ports 和 expose 差異

在 docker-compose 中有兩種方法可以暴露容器 ports,

分別是 ports 和 expose,

ports

...
ports:
  - "5000:5000"  # 绑定 container 中的 5000 port 到 本機(HOST) 的 5000 port
  # (HOST:CONTAINER)

  - "5001:5000"  # 绑定 container 中的 5000 port 到 本機(HOST) 的 5001 port

  - "5000"       # 绑定 container 中的 5000 port 到本機的任意 port (本機會隨機被分配到一個 port)
...

隨機 port 範例,

這邊使用 dpage/pgadmin4 這個 images 來示範,

docker run -p 80 \
    -e "[email protected]" \
    -e "PGADMIN_DEFAULT_PASSWORD=SuperSecret" \
    -d dpage/pgadmin4

如果我們執行兩次以上指令,你會發現本機被分配到兩個隨機的 ports (如下圖),

alt tag

本機被隨機分配到 32768 以及 32769 port,

這邊不管我們怎麼設定 ports,這些 ports 都會暴露給本機 (HOST) 以及其他 containers,這點很重要:exclamation::exclamation:

也就是說,如果本機 5001 ports 被使用了,其他的 containers 就無法使用 5001 ports,

可能要改成5002 ports 之類的。

expoese

...
expose:
  - "4000"
  - "6000"
...

expose 是將 port 暴露給其他容器。

expose 和 ports 最大的差別就是在 expose 不會暴露 port 給本機(HOST),

所以 本機(HOST)絕對無法被訪問,但 containers 內可以被訪問,

所以說如果今天你的容器想要在 本機(HOST) 被訪問,一定要使用 ports 方式。

ports 和 expose 差異

簡單說,就是 ports 可以被 本機(HOST) 和 containers 訪問 ; 而

expose 是本機(HOST) 無法被訪問,只有在 containers 中可以被訪問。

Docker container 內如何連接到本機 localhost 服務

alt tag

假設今天在本機上有一個 A 服務, 他是使用 docker run 起來的,

而本機上還有一個 B 服務, 是用 vscode run 起來的 (非 docker),

這時候我有一個需求, 我想要將我的 A 服務連線到我的 B 服務,

也就是從 docker 內的服務連接到本機 localhost.

比較簡單的方法, 就是透過 docker 內的這個參數,

host.docker.internal:host-gateway.

在你的 yml 裡面加上,

version: '3.5'
services:

  web:
    ......
    extra_hosts:
      - "host.docker.internal:host-gateway"
......

這樣當你在容器內, 就可以順利訪問本機:smile:

curl http://host.docker.internal:8069

也可以參考 docker compose 安裝 pgadmin4,

假設今天不考慮使用網路的方法, 如果一個容器 db 是在 5432, 另一個容器是 pgadmin4,

這樣要怎麼透過 pgadmin4 連接到我的本機的 5432 呢😵‍💫

答案就是使用 host.docker.internal:host-gateway.

Docker Registry

可以把它想成是一個類似 github 的地方,只不過裡面變成是存 docker 的東西,當然,

也可以自己架,但會有一些額外的成本,像是網路,維護等等,這部分就要自己衡量了:grinning:

接下來教大家如何將 image push 到 Docker Registry :smiley:

首先,先登入 Docker Registry ( 註冊流程很簡單,我就跳過了 )

docker login

舉個例子,先 run 一個 busybox 的容器

docker run -it busybox

接著在裡面新增一筆資料

 echo 'text' > data.txt

然後打開另一個 terminal ,使用 docker ps 查看目前容器的 id

再來使用像 git 一樣的方式 commit

docker commit

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

可參考 https://docs.docker.com/engine/reference/commandline/commit/

docker commit -m "test" 4fb4ef51e917 twtrubiks/my_busybox

-m commit message ,和 git 一樣。

twtrubiks/my_busybox 則為我們定義的 REPOSITORY。

如果需要 tag , 也可以增加

docker commit -m "test" 4fb4ef51e917 twtrubiks/my_busybox:v1

( 如果沒定義 tag , 則會顯示 latest )

這時候可以用 docker images 查看

最後 push

docker push twtrubiks/my_busybox

docker 是一層一層的概念,他只會 push 自己新增的幾層上去而已,

所以不用擔心整個 image 很大,要上傳很久 :relaxed:

最後可以到 https://hub.docker.com/ 確認是否有成功 :smile:

用 Docker 實戰 Django 以及 Postgre

上面介紹了那麼多,來實戰一下是必須的 :satisfied:

我們使用 Django-REST-framework 基本教學 - 從無到有 DRF-Beginners-Guide 來當範例

有幾個地方必須修改一下,

settings.py 裡面的 db 連線改成 PostgreSQL

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': 'password123',
        'HOST': 'db',
        'PORT': 5432,
    }
}

建議也將 ALLOWED_HOSTS = [] 改為 ALLOWED_HOSTS = ['*'] ( 這只是方便,實務上不會這樣使用 )

再來是兩個很重要的檔案,分別為 Dockerfiledocker-compose.yml

Dockerfile

FROM python:3.8.12
LABEL maintainer twtrubiks
ENV PYTHONUNBUFFERED 1
RUN mkdir /docker_api
WORKDIR /docker_api
COPY . /docker_api/
RUN pip install -r requirements.txt

詳細可參考 https://docs.docker.com/engine/reference/builder/

docker-compose.yml

version: '3'
services:

    db:
      container_name: 'postgres'
      image: postgres
      environment:
        POSTGRES_PASSWORD: password123
      ports:
        - "5432:5432"
        # (HOST:CONTAINER)
      volumes:
        - pgdata:/var/lib/postgresql/data/

    web:
      build: ./api
      command: python manage.py runserver 0.0.0.0:8000
      restart: always
      volumes:
        - api_data:/docker_api
        # (HOST:CONTAINER)
      ports:
        - "8000:8000"
        # (HOST:CONTAINER)
      depends_on:
        - db

volumes:
    api_data:
    pgdata:

詳細可參考 https://docs.docker.com/compose/compose-file/#compose-file-structure-and-examples

溫馨小提醒 1 :heart:

可能有人會問為什麼我是使用 0.0.0.0,而不是使用 127.0.0.1:question::question:

python manage.py runserver 0.0.0.0:8000

127.0.0.1,並不代表真正的 本機,我們經常認為他是本機是因為我們電腦的 host 預設都幫你設定好了:smirk:

詳細的 host 設定教學可參考 hosts-設定檔 以及 查詢內網 ip

0.0.0.0 才是真正的代表,當下 ( 本 ) 網路中的本機 :pencil2:

如果大家想更深入的了解,可 google 再進一步的了解 127.0.0.1 以及 0.0.0.0 的差異 :smile:

溫馨小提醒 2 :heart:

這邊要特別提一下 depends_on 這個參數,

詳細可參考 https://docs.docker.com/compose/compose-file/#depends_on

上面連結中有一段說明很值得看

*depends_on does not wait for db and redis to be 「ready」 before starting web - only until they have been started. If you need to wait for a service to be ready, see Controlling startup order for more on this problem and strategies for solving it.*

以我的 docker-compose.yml 為例,啟動順序雖然為 db -> web,但他不會等待 db 啟動完成後才啟動 web

也就是說,還是有可能 web 比 db 先啟動完成,這樣就需要重啟 web service,否則會無法連上 db :sob:

如果真的要控制啟動順序,請參考 Controlling startup order

溫馨小提醒 3 :heart:

docker-compose.yml 其實使用 docker run 也是可以完成的,例如這個範例中,如果使用

docker run 來寫,會變成這樣。

首先,為了讓容器彼此可以溝通,我們先建立一個網路 ( User-defined networks ),

docker network create my_network

db 容器

docker run --name db -v pgdata:/var/lib/postgresql/data/ -p 5432:5432 --network=my_network -e POSTGRES_PASSWORD=password123 postgres

接下來先去 api 資料夾中 build 出 image

docker build --tag web_image .

--tag , -t , tag 這個 image 名稱為 web_image

也可以是

docker build -t user/repo:tag .

web 容器

docker run --name web -v api_data:/docker_api -p 8000:8000 --network=my_network --restart always web_image python manage.py runserver 0.0.0.0:8000

以上這樣,和 docker-compose.yml 其實是一樣的:open_mouth:

設定完了之後,接下來我們就可以啟動他了

docker-compose up

接下來你會看到類似的畫面

假如你出現了類似的畫面

代表 database 還在建立的時候,你的 web ( Django ) 就去連接他,

所以導致連接不上,這時候我們可以先終止他 ( 按 Ctrl+C )

接著在重新 docker-compose up

我們成功啟動了 ( db 連線也正常 )

:exclamation: commit 已經更新為自動 migrate:exclamation:

但你仔細看上圖,你會發現他說你還沒 migrate

接下來我們開啟另一個 cmd 進入 web 的 service,

透過剛剛介紹的指令進入 service

docker ps
docker exec -it <Container ID> bash

或是說也可以從 Kitematic 進入,

進入後我們可以開始 migrate

python manage.py makemigrations musics
python manage.py migrate

順便在建立一個 superuser

python manage.py createsuperuser

:exclamation: commit 已經更新為自動建立 superuser:exclamation:

請參考 docker-compose.yml 中的 environment ( 如下 ),

DJANGO_SUPERUSER_USERNAME DJANGO_SUPERUSER_PASSWORD DJANGO_SUPERUSER_EMAIL

接著我們可以試著使用 GUI 介紹連接 db,

因為我們是用 PostgreSQL ,可以透過 pgadmin 連線

我們剛剛 migrate 的東西確實有存在

我們不需要再重新啟動

直接可以開開心心的去瀏覽 http://127.0.0.1:8000/api/music/

大家一定會看到很熟悉的畫面

接著依照自己剛剛設定的帳密登入進去即可

以上整個環境,都是在 Docker 中 :open_mouth:

如果我們再 Ctrl+C 退出,重新啟動一次 docker-compose up

這次就不會再和你說你沒有 migrate 了

其他管理 Docker GUI 的工具

除了 Kitematic 之外,還有其他不錯的推薦給大家,

這次要介紹的就是 portainer 功能強大又好用 :fire:

其實如果去看看 Kitematic 以及 portainer 的 github,

你會發現 portainer 感覺比較有在 maintenance :smile:

而且我使用了 portainer 之後,真心大推 :smiley:

安裝方法可參考 https://portainer.io/install.html

docker volume create portainer_data
docker run --name=portainer -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

-d -p 在前面的 docker run 有介紹過代表的含意,--name 只是命名而已。

Note 1: The -v /var/run/docker.sock:/var/run/docker.sock option is available on Linux environments only.

Note 2: The -v portainer_data:/data portainer/portainer option will persist Portainer data in portainer_data on the host where Portainer is running. You can specify another location on your filesystem.

( 建立起來之後,就依照 container 的操作即可 )

之後查看 http://localhost:9000/ 就會看到下圖

然後設定帳號、密碼

選 Local or Remote

畫面真的不錯看,而且資訊也很豐富 :heart_eyes:

相信我,你使用完他之後,你會默默的邊緣化 Kitematic :smirk:

查看 port 佔用狀況

這個推薦給大家,有時候會遇到 port 被佔用,用指令查比較方便

Linux

安裝 net-tools

sudo apt install net-tools

查看誰佔用 80 port

sudo netstat -lnp | grep -w ':80'

-l, --listening display listening server sockets.

-n, --numeric don't resolve names.

-p, --programs display PID/Program name for sockets.

也可以使用 lsof

sudo lsof -i :80

-i select IPv[46] files.

Windows

查看所有 port 的佔用狀況

netstat -ano

查看指定 port 的佔用狀況,例如現在想要查看 port 5432 佔用的狀況

netstat -aon|findstr "5432"

查看 PID 對應的 process

tasklist|findstr "2016"

停止 PID 為 6093 的 process

taskkill /f /PID 6093

停止 vscode.exe process

taskkill /f /t /im vscode.exe

MAC

將 port 為 8000 的 process 全部停止

sudo lsof -t -i tcp:8000 | xargs kill -9

查看指定 port 的佔用狀況,例如現在想要查看 port 5432 佔用的狀況

lsof -i tcp:5432

在 Linux 中自動啟動 docker

在 Linux 中自動啟動 docker

如何清除 Docker container log

Youtube Tutorial - 如何清除 Docker container log

docker 的 container log 都會在 /var/lib/docker/containers 裡面

( 前提是你使用官方的安裝方法, Youtube Tutorial - Ubuntu(Linux) 如何安裝 docker)

如果你是使用 snap 安裝 docker, 路徑則會在 /var/snap/docker/common/var-lib-docker/containers.

alt tag

log 是一個 json 的檔案

alt tag

如果你一直不去管他, log 就會越來越大:scream:

以下狀況這個 log 會被清除, 就是修改了 docker-compose.yml 或是

你執行了 docker-compose down, 這些 logs 都會被清除 (因為 containers 重新建立).

(docker-compose stop 不受影響, 因為只是暫停而已)

建立大家可參考 docker-compose up/down 和 restart 的差異

那你可能會問我, 如果我很長一段時間都不會修改 docker-compose.yml 以及執行

docker-compose down 該怎麼辦:sob: (因為 log 可能會長很快)

這邊提供大家一個方法, 使用 linux 中的 truncate 指令(可參考 Linux 指令教學 - truncate)

刪除全部 container 的 logs

truncate -s 0 /var/lib/docker/containers/*/*-json.log

但是有時候只希望針對(清除)某個 container 的 logs, 這時候就可以使用以下的指令

truncate -s 0 $(docker inspect --format='{{.LogPath}}' <container_name_or_id>)

(container_name_or_id 請換上自己 container 的 id 或 name)

其中的 docker inspect --format='{{.LogPath}}' <container_name_or_id> 只是顯示路徑而已.

alt tag

但還有一個更好的方法, 直接透過 docker 內的 JSON File logging driver.

JSON File logging driver

Youtube Tutorial - Docker 中的 JSON File logging driver(container log)

在 docker 中 json-file driver 是默認的 default logging driver, 詳細可參考 json-file

所以我們可以透過這個設定限制 log 的大小,

logging:
  driver: "json-file"
  options:
    max-file: "1"    # default 是 1
    max-size: "200m" # default 是 -1, 也就是沒有限制

設定完之後重新啟動 docker-compose, 可以使用以下的指令查看是否生效

docker inspect --format '{{.HostConfig.LogConfig}}' CONTAINER

alt tag

這樣設定完之後, 就不用再擔心 container log 吃掉大量的容量了:smile:

Health Check

直接來看一個範例 docker-compose.yml

version: '3.5'
services:
  web:
    image: odoo:17.0
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "8069:8069"
    healthcheck:
      test: curl -fs http://localhost:8069/web/database/selector || exit 1
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - odoo-web-data:/var/lib/odoo
      - ./config:/etc/odoo

  db:
    image: postgres:16
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=odoo
      - POSTGRES_PASSWORD=odoo
      - PGDATA=/var/lib/postgresql/data/pgdata
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U odoo"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - odoo-db-data:/var/lib/postgresql/data/pgdata

volumes:
    odoo-web-data:
    odoo-db-data:

當執行時, 你會發現多了一個 health: starting 如下圖,

alt tag

當(每)過了 10 秒 (interval: 10s) 之後, 如果順利啟動會變成 (healthy) 如下圖,

alt tag

當(每)過了 10 秒之後, 如果連續失敗很多次 (retries: 5),

則會顯示 (unhealthy) 如下圖,

alt tag

docker 的 Health Check 會回傳你數字,

0 代表成功,container is healthy

1 代表失敗,假設失敗超過指定次數(retries: 5), container is unhealthy

至於 depends_on 底下的 condition: service_healthy 代表必須檢查通過,

才會啟動, 可參考 Control startup, 有以下三種,

service_started 如果沒有特別指定, 就是這一種.

service_healthy

service_completed_successfully

後記:

Docker 算是我最近才開始接觸的,所以也算是新手,如果我有任何講錯的,歡迎和我說,我會再修改 :grinning:

Docker 可以玩的真的很多,延伸參考

也可以再玩玩 Docker Swarm ( 分散式系統 ) :satisfied:

最後,希望大家在學習 Docker 的過程中,遇到不懂的,可以去找資料並且了解他, 順便補足一些之前不足的知識。

執行環境

  • Mac
  • Python 3.8.12
  • windows 10

Reference

Donation

文章都是我自己研究內化後原創,如果有幫助到您,也想鼓勵我的話,歡迎請我喝一杯咖啡:laughing:

綠界科技ECPAY ( 不需註冊會員 )

alt tag

贊助者付款

歐付寶 ( 需註冊會員 )

alt tag

贊助者付款

贊助名單

贊助名單

License

MIT license

Stars
1645
1.22% more than last month
Forks
298
Open Issues
5