Docker 筆記

雖然之前有聽過一堂關於 Docker 的課程,但一直沒有實際應用。
這次修了一堂課,並指定要用 Docker 架點東西,於是有了這份筆記。

Docker 簡介

一個輕量的虛擬化技術。

有時候我們只是要建立一個 "服務",但單為這個服務開一個 VM 會非常耗資源。
(撇開儲存空間,啟動一個 VM 再怎麼說也是要不少 Ram 以及執行緒去操作 Hypervisor)

這時候 Docker 這樣的服務就會很有用,我覺得你也可以把它想像成 Android 手機大家常常討論的
" APP 多開程式 "。

想想看,在以前,一隻手機只能登一個 Line 帳號。
如果我想要同時操作兩個帳號,就必須要有兩支手機。(撇開重簽 APK)
今天有了多開程式,簡化了所需的資源(手機),也省去了很多步驟 (操作另一隻手機,重裝 APP ... 等)
即使你用 PC 好了,PC 多開手機模擬器
但肯定還是沒有一個模擬器裡面同時操作兩個 APP 來的方便。

Docker 就是像這樣的存在,故在現今也有很高的利用價值。

Docker 行為

  • 建立 ( docker container create )
  • 執行 ( docker container start )
  • 進入或修改內容 ( docker container attach, docker container exec… )
  • 暫停 ( docker container pause )
  • 停止 ( docker container stop )
  • 移除 ( docker container rm )

Docker 指令

  • 取得 Image : docker image pull [Image Name]
  • 建立 Container : docker container create [options] [Image Name] [command (opt)]
    • --name, --interactive, --tty, --publish, --volume, --workdir
    • 如果不是用 create 而是用 run,會是建立容器並執行
    • 執行時可以加參數限制資源 : --cpuset-cpus=1
  • 執行 Container : docker container start [options] [Container Name]
    • --attach : Attach STDOUT/STDERR and forward signals
    • --interactive : Attach container’s STDIN
  • 查詢 Container : docker container ls [options]
    • --all, --quiet, --size
    • 舊版本是 docker container ps,或是簡化成 docker ps,都能用
  • 查看 Container 執行結果 : docker container logs [options] [Container Name]
    • --follow, --time
  • 進入 執行中 Container : docker container attach [Container Name]
    • 進入 執行中 Container : docker container exec [options] [Container Name] [command (opt)]
    • Ver 1.3+
    • --env, --interactive, --tty, --user, --workdir

以下是一些操作指令

  • 交換資料 : docker container cp [Local path] [Container Name:Path]
    • 上述是 Local 複製到 Container 內,反過來的話兩個參數對調即可。
  • 檢視設定 : docker container inspect [Container Name]
    • 更新設定則使用 update
  • Top : docker container top [Container Name]
    • 類似 Linux 的 top
  • Status : docker container status [options]
    • --all
  • Diff : docker container diff [Container Name]
  • 暫停 : docker container pause [Container Name]
  • 停止 : docker container stop [options] [Container Name]
    • --time
  • 移除已停止的 Container : docker container rm [Container Name]
    • 強制移除 : -f,但不建議使用。
  • 更改標籤 : docker container rename [Container Name] [New Name]

Image 操作

  • 匯出 (備份) : docker container export [Container Name] [-o new_name]
  • 匯入 Container 的備份成 Image : docker image import [filename] [New_Container_Name[:tag]]
  • 匯入網路上的 Container backup 成 Image : ``docker image import [url] [New_Container_Name[:tag]]

在找 image 時可能會遇到的操作

  • 搜尋 : docker search [options] [Keyword]

  • 下載 : docker pull [Image Name]

  • 查看 local 使用 ls,刪除已下載的用 rm,查看細節用 inspect (前面已介紹過)

  • 紀錄 : ``docker image history [Image Name]

  • 現有 Container 製作成 Image : ``docker container commit [options] [Container Name] [Image Name]

    • --author, --change, --message, --pause
  • 透過 Dockerfile build Image : docker image build [OPTIONS] PATH | URL |

  • Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE:

    • docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
  • push 到 Registry : docker image push [Image_Name[:標籤]]

  • 儲存 Image 為檔案 : docker image save [Image_Name[:tag]] > [File_Name]

    • 和 container 的 export 差不多
  • 從檔案載入 Image : docker image load [Image_Name[:tag]] < [File_Name]

    • 改箭頭而已

流程舉例

1
2
3
4
5
6
7
docker image pull bash
docker container create -it --name test-bash bash
docker container ps
docker container start -ai test-bash
docker container logs test-bash
docker container rm test-bash
docker container ps -a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 建立
docker pull python
docker image ls
docker create --name test-python python python /hello.py
# (第一個 python 是 image 名稱,再後面的 python /hello.py 是一個 command)
docker ps -a
# (預先撰寫好 hello.py)
docker cp /root/hello.py test-python:/hello.py
docker start -i tset-python

# 匯出
docker container ps -a
docker container export test-python > test-python.tar
ls -l test-python.tar

# 移除
docker container rm test-python
docker image rm python

# 匯入/還原
docker image import tset-python.tar test-python:backup
docker image ls

# 再 run 一次
docker run -it --name test-python test-python:backup python /hello.py
docker container ps -a


Image 製作

使用 dockerfile 製作,優點 :

  • 純文字,檔案小,分享快
  • 版本管理
  • 應該使用一個空目錄來裝載 dockerfile

常見語法

  • 註解 : #
  • Image Base : FROM image[:tag]
  • 標籤 : LABEL key=value
  • 定義變數 : ENV name=value, ARG name=value
  • 新增檔案 : ADD src dest, COPY src dest
  • 執行指令 : RUN command para1 para2, CMD, ENTRYPOINT
  • 工作目錄 : WORKDIR /path/../..
  • 指定使用者 : USER user[:group]
  • 對外 Listen Port : EXPOSE port [/protocol]
    • 僅是宣告用途,run 的時候要自己加參數去做對應。
    • 呈上,-p <host port>:<container port>
  • 掛載實體位置 : VOLUME [path]

實際運行與架設

環境 : Ubuntu 20.04, on AWS Lightsial.

  • 在 AWS Lightsial 給的 VM 中,預設的 account 叫做 ubuntu,直接可以免密碼做 sudo。
    如果仍想要切 root 的 account,可以執行 sudo bash
  • 如果想要用 Local Terminal 做 ssh,可以先把 Local 的 id_rsa.pub 內的內容複製到 VM 中的 authorized_keys 裡面。

在 Ubuntu 20.04 安裝 Docker

跟著跑就好。

1
2
3
4
5
6
7
8
9
sudo apt update
sudo apt upgrade
sudo apt install docker.io
sudo systemctl enable --now docker
# 上面這個跑完會有一個 docker.service -> /lib/... 的訊息
# 如果想要關閉服務,可以使用 sudo systemctl disable --now docker

docker --version
# 我這邊目前是 19.03.8

然後可以開始跑 Hello-world 程式
docker run hello-world

但是高機率會遇到一個錯誤訊息 :

1
docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.40/containers/create: dial unix /var/run/docker.sock: connect: permission denied.

解法 :

1
2
3
sudo groupadd docker
sudo usermod -aG docker $USER
# $USER 改成你的 account 名稱,例如 ubuntu

之後建議先做 logout,然後重新登入,這樣才會生效。
(若仍然沒效可以考慮直接 sudo chmod 777 /var/run/docker.sock)

以上都做完之後,試著跑跑看吧。docker run hello-world

有看到出現這段,基本上應該就沒問題了。

1
2
Hello from Docker!
This message shows that your installation appears to be working correctly.

安裝 Apache 的 Docker 包

1
2
3
docker search httpd
docker pull docker.io/httpd
docker images # 亦可使用 docker image ls # 查詢特定名字可用 docker images httpd

為了練習使用 dockfile,透過寫一個 dockfile 來建立 image

1
2
3
4
FROM httpd:latest
LABEL description="build a web-server with docker"
EXPOSE 8888
VOLUME

搭配使用的 build 指令 :

1
2
docker build -t WebServer/apache_proj .
# 最後面使用 "." 代表此目錄,也就是 Dockerfile 存在的目錄。

先用 docker images 確認一下

搭配使用的 run 的指令 :

1
2
3
4
5
6
7
8
9
10
sudo docker run -itd --name apache_proj -p 8888:80 -v ~/dk_workenv/html/:/usr/local/apache2/htdocs/ webserver/apache_proj

# sudo docker run -itd --name NAME -p host_port:container_port -v host_path:container_path REPOSITORY
# REPOSITORY 的 完整資訊要打出來,例如我的是 webserver/apache_proj
# 或是也可以使用 IMAGE ID 來替代。

# 對應 path 是為了更彈性,host_path 內的東西和 container_path 內的是共用的。
# 這樣我不用每次都 cp 東西進去 container,而是直接同步。

# 對應 port,我的 Server 別人從瀏覽器的 8888 port,可以連到我 container 內 80 port 的服務。

docker ps 看一下執行中的 Container

然後我很高興的跑去瀏覽器試試 http://ip:8888
但是完全連不上。

先做 localhost 檢查 curl localhost:8080
有看到東西。至少我的 volume 沒有出問題。

然後先用 docker container logs apache_proj 查看一下目前 log
再用 curl localhost:8080 去連接,再 logs 一次,有看到關鍵回饋 :

1
[10/Oct/2020:17:04:02 +0000] "GET / HTTP/1.1" 200 5

感覺都沒問題啊。
我可以從 VM 內去連接 Container 內的 80 port,然後從 VM 的 8888 出去。
... 那為何外界無法接到我的 8888 port ?

於是跟著這個想法,我去了 Lightsail 的控制台 ... 默默的把 TCP 8888 port 打開 ...
... 成功了。

(請各位如果是用 VPS,記得去把 port 打開 ...。)

Docker-Compose

Docker-Compose 的用途是用來管理多個 image 成一個執行體
例如 wordpress + mySQL 服務一起搭建,我只需要寫好 docker-compose.yml
然後下個啟動指令,兩個 image 都能從 container 形式啟動。
並且, docker-compose 還可以放很多參數 (docker container run [...] 所指定的參數)
也能將 Dockerfile 整合進來,可以說是非常方便,也很推薦使用。

第一次摸索時很排斥參數一大堆又不知道怎麼去配合,下面做點有使用和沒使用的情境 :

  • 假設我使用 docker-compose :
    • 使用指令方式將 image 啟動成 container (或是我們透過 Dockerfile,同樣效力)
      • 舉例 : docker container create -it --name apache_proj httpd
      • 或是透過 Dockerfile,寫好 Dockerfile 後執行
        • docker build -t my_webserver/apache_proj .
    • 啟動成 container 之後,使用 sudo docker run -itd --name apache_proj -p 8888:80 -v ~/dk_workenv/html/:/usr/local/apache2/htdocs/ my_webserver/apache_proj

沒錯,就是這麼長。

  • 假設我有使用 docker-compose :
    • 寫好 Dockerfile,放在當前目錄 (請參考前一個段落,有範例)
    • 寫好一個 docker-compose.yml,範例在下面。
      • 我們可以觀察到,我把 docker run 所需要的參數都放進去 docker-compose 檔案裡面了。
    • 然後跑 docker-compose up -d 這樣就好了。
docker-compose.yml
1
2
3
4
5
6
7
8
9
version: '3.8'
services:
apache_proj:
build: .
image: webserver/apache_proj:try
ports:
- "8888:80" # out:in
volumes:
- ~/dk_workenv/html/:/usr/local/apache2/htdocs/

變得簡潔很多,很有條理,不需要自己一直重複打那些參數做一樣的事情。

那,如果,今天我已經有做好 image,只是想透過 docker-compose 執行 ?
(也就是我不想用 docker run)

docker-compose up -d --no-build

  • -d 代表 detach,若你沒加的話,你當前的 terminal 就會直接 print 出 container 執行中的 log。
    如果加了,他就會在 background 執行,你可以繼續用當前的 terminal 繼續做其他事。
  • --no-build 就代表了不重新 build image。

使用方式差不多是這樣。

Ref

A tutorial about Docker
Docker - 第十三章 | 安裝Apache Server
利用 Dockfile、Docker Compose 建立 LAMP 環境 (PHP、Apache、MySQL)

End
-----------------------------------

2020.10.09