# NCKU SA/NA Course - 03. Docker ## Outline - 甚麼是 Docker - Docker 與 VM 差在哪 - Docker 基本概念 - Docker Image - 什麼是 docker image - Docker Container - 什麼是 docker container - Docker Image vs Docker Container - Docker Repository - Docker Hub - docker search - docker push - privicy place - docker-registry - Docker install - sudo apt update - sudo apt-get remove docker docker-engine docker.io - sudo apt install docker.io - docker --version - Docker Image - docker pull - docker images - docker commit - Dockerfile - save and load image - remove docker image - Docker Container - run - docker run - process https://blog.csdn.net/u013190088/article/details/81255804 - docker start - Daemonized - docker ps - docker logs - stop - docker stop - --restart - entry container - (*)docker exec - docker attach - save and load container - docker export - docker import - remove docker container - Manage application data - bind mount - volumes - bind mount vs volumes - Dockerfile - struct - command - build - port Forwarding - https://ithelp.ithome.com.tw/articles/10193291 - docker image to Dockerfile - https://github.com/CenturyLinkLabs/dockerfile-from-image - Docker compose - what is docker-compose - Docker ground knowledge - https://st.vincent55.tw/DWZ - https://st.vincent55.tw/5Gn - https://kknews.cc/zh-tw/code/gp9lzve.html - implement tech - Linux Kernel namespace cgroup - namespace - cgroup - Archtech - docker-server: Docker Daemon - docker-client: Docker Client practice lab : https://github.com/Vincent550102/nasa-docker-lab ## 甚麼是 Docker > Docker 是一個開放原始碼軟體,是一個開放平台,用於開發應用、交付應用、執行應用。 Docker允許使用者將基礎設施中的應用單獨分割出來,形成更小的顆粒,從而提高交付軟體的速度。 Docker容器與虛擬機器類似,但二者在原理上不同。[Reference]([/vXSvgBwET4aX26eD8b6M_g](https://zh.wikipedia.org/wiki/Docker)) - 一個開源並讓你能快速建立、測試、部署應用程式的軟體平台 - 一種使用 Golang 實作的輕量級的作業系統虛擬化解決方案 - 在 LXC(Linux Container) 的基礎上 Docker 進行了進一步的封裝 - 可將應用程式與其環境打包,使其移植十分方便  ### 若遇到了以下的情境  #### 解決方案 1. Virtual environment 2. Virtual machine 3. Docker #### Virtual environment > 有些情況不能純使用虛擬環境,此方法不通用(跨平台)  #### Virtual machine、Docker 是較為實用的方法 ## Docker Container 與 VM 差在哪 ### 虛擬化技術(以抽象程度) - 硬體層級虛擬化(Hardware Abstraction Level) - 透過 Hypervisor,在主體機器之上運行多台完整客體機器 - ex. VirtualBox、Hyper-V、VMware - 作業系統層級虛擬化(Operating System Level) - 又稱容器化,將作業系統核心虛擬化,可使容器共用同個 Host OS - ex. Docker、LXC、KVM - 程式語言虛擬化(Programming Language Level) - 高階語言轉譯成bytecode,實現跨作業系統、跨語言 - ec. Microsoft NET、Oracle Java、Parrot  虛擬機需要虛擬出完整的機器,包括 OS、硬體環境、Memory 等等,並將其包裝於 VM images,而 Docker 則是透過 Docker Engine 來調度 images 與使其產生 Container。 值得一提的是,在非 Linux 作業系統下,為何仍需要一個 Hypervisor,並虛擬出一台 Guest Linux。因為 Docker 在底層設計時,使用到了 Linux Kernel 當中,用來虛擬化的技術 namespace 與 Cgroup。 有關底層設計,在 # 會更進一步提到。 以下表格將比較兩者異同 | 比較 | VM | Docker | |-------------------- |---------------------- |------------------------------ | | 啟動時間 | 分鐘級 | 秒級 | | Memory 效率 | 低,需要加載整個系統 | 高,只需加載滿足運行必要配置 | | 存儲使用 | GB | MB | | 一般系統最大承載量 | 十幾個 | 上千個 | ## Docker Components基本概念  ### Docker images Docker 映像檔為一個唯獨的模板,包含了構建 Docker Container 的必要資訊,Docker image 可以是多個 Docker image 堆疊而成,如下圖。  構建出 Docker image 大致有三種方式 1. 從 Docker Repository 拉取下來,例如 Docker Hub 2. 在其他環境使用匯出 Docker image 並在自己電腦上匯入 3. 使用 Dockerfile 自行配置 image ### Docker Container Docker Container 是一個透過 Docker image 執行起來的 Process,可以想像為 Docker images 所建立的一個實例,每個 images 可建立多個互相隔離的 Docker Container。 Docker Container 支援開始、停止、修改、刪除的操作。上面有提到 Docker image 是個唯獨(read-only) 的檔案,Docker container 在 Docker image 最上層加上一層可讀可寫層。 Docker Container 之間是隔離的,正常使用下不會發生 port 衝突的問題。  ### Docker images vs Container  ### Docker registry registry 是集中存放映像檔的地方,中文稱作倉庫,倉庫又區分為公有倉庫與私有倉庫。 - 公有倉庫 其中公有倉庫最常使用的為 Docker 官方維護的 [Docker Hub](https://hub.docker.com/),大部分我們需要的現成映像檔都會從這去拉取,我們可以透過 `docker search [OPTIONS] TERM`去搜尋官方倉庫中的映像檔。  可以使用 `--filter=stars=[startnumber]` 來限定只會出現幾顆星星以上的映像檔。  登入後可用 `docker push` 將映像檔放上 Docker Hub - 私有倉庫 在某些情況下,不方便使用像 Docker Hub 的公開倉庫,就會需要使用私有倉庫來儲存映像檔,有關私有倉庫的架設可以使用[docker-registry](https://github.com/docker/docker-registry)。 officeal github dockerhub demo ## Docker 下載 1. 將系統更新 `sudo apt update` 2. 將舊版本 docker 移除 `sudo apt-get remove docker docker-engine docker.io` 3. 安裝 docker `sudo apt install docker.io` 4. 檢查 docker 是否安裝成功 `docker --version`  ## Docker Image ### 拉取現有映像檔(docker pull) 從公開或私有的倉庫拉取 image,預設是 Docker Hub 公共註冊伺服器中的 Repository。  ### 檢視當前映像檔(docker images) 檢視當前存在的 docker image,是 `docker image list` 的別名  ### 修改已有映像檔(docker commit) 若想修改已有映像檔,則需使用`docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]` 我們可以先製作一個映像檔實例,也就是容器,並在該目錄創建一個隨意檔案。  完成修改後,我們退出 Docker 介面,之後在 Host 下 `sudo docker commit -m "change file" 4ea57b5196be ubuntu:v2` ,其中 f4fd69e6adad 為經修改之 Container ID,ubuntu 及 v2 分別為 REPOSITORY 與 TAG。之後確認 `sudo docker images`,發現新出現了一個 docker image(畫面中的第一個)。  並用該 docker image 重新創建一個容器實例,發現是經過修改後的結果。  ### 自行建立並設計映像檔(Dockerfile/docker build) 對於小型的個人專案,透過 `docker commit` 來擴展一個 Docker Image 還算是一個方法,但若場景換到多人合作與大型專案需要進行分享就不適合。 Docker 提供了簡單的方式來自定義一個 image,我們只要寫好 Dockerfile 後,執行 `docker build` 便可自動化的配置好我們的 Docker image。Dockerfile 內每一行皆是用來建立映像檔的指令(Instruction)。 我們準備好一個空目錄,並在裡面放入一個包含 `print('Hello Docker!')` 名為 `hello.py`的 python 文件。  再來,我們在該路徑下新增包含以下內容的 Dockerfile  ```dockerfile # My First Dockerfile FROM python:3.8 RUN apt-get -qq update COPY hello.py /app/ CMD ["python3", "/app/hello.py"] ``` - 其中 # 為註解 - FROM:使用到的基底映像名稱 - RUN:後面擺放 Linux 指令用來執行設定 Image 需要的環境 - COPY:將本地的檔案複製到 Docker 中,類似的指令有 `ADD` - CMD:在執行完 docker run 後,會自動執行的指令 之後,我們在與 Dockerfile 同層的資料夾運行 `docker build` 指令 ```shell sudo docker build -t mydockerfile . ``` 可使用 -t 來指定 image 的 tag,後方的 . 代表 Dockerfile 位於當前目錄下。 ::: info 在 docker build 的過程中 Dockerfile 中遇到 docker images 中沒有的 Image,將會自動 docker pull ::: 執行結果如下。  我們檢查 `docker images`,並用 docker run 創建容器實例,看是否會印出東西。  ### 移除映像檔(docker rm) 若要移除某個 images 可以使用以下指令。若要強制刪除 image 可以加上 `-f` 來 force remove。 ```shell docker rmi [OPTION] [IMAGE ID] ```  以下指令能一次強制刪除所有的 images ```shell docker rmi -f $(docker images -aq) ``` ### 匯出與匯入映像檔(docker save/ docker load) #### docker save ```shell sudo docker save -o FILENAME IMAGE [IMAGE...] ```  #### docker load 載入匯出後的 images 檔案。 ```shell sudo docker load -i [FILENAME] ```  ## Docker Container ### 檢視當前容器(docker ps) 檢視當前存活的容器之相關資訊,指令格式如下 ```shell docker ps [OPTIONS] ``` 可以使用 `-a` 檢視所有容器(包含已終止的容器) ```shell docker ps -a ``` ### 啟動一個容器(docker run/ docker start) #### 建立容器實例並執行(docker run) 指令格式如下 ```shell docker run [OPTIONS] IMAGE [COMMAND] [ARG...] ``` ```shell sudo docker run ubuntu:v2 echo 'Hello! container' ``` * **ubuntu:v2**:指定需要建立容器的 docker images * **echo 'Hello! container'**:指定啟動後需要運行什麼指令  以下簡列 docker run 後的步驟 1. 檢查本地是否有該映像檔,否則自動使用 `git pull` 2. 利用該映像檔創建並啟動一個容器 3. 分配一個 file system,並在映像檔外掛載一個可讀寫層 4. 從宿主主機上網路橋接口橋接一個到該容器內 5. 分配一個 ip 給該容器 6. 執行指定的應用程式 7. 執行完畢後容器終止(若沒特別指定 Daemonized) 在很多時候,我們會想要進入容器互動,因此我們可以為容器分配一個虛擬終端(`-t`)並讓標準輸入保持打開(`-i`),這兩個參數可擺在同個 - 後面(`-it`)。  若想在容器主程序一結束時,立刻移除容器可使用 `--rm`。 ### docker logs 取得容器的 logs,指令格式如下 ```shell docker logs [OPTIONS] CONTAINER ``` ### 守護態執行(Daemonized -d) 有時候我們會想要一些服務是在後台運行的,我們就可以在啟動時使用 `-d` 將該容器以守護態執行,這時就不會將結果輸出(`STDOUT`)在宿主機上。 ```shell sudo docker run -dit ubuntu:v2 bash -c "while true; do echo hello; sleep 1; done" ```  :::info 可以比較看看有加 -d 與沒加的差別 ::: ### 終止一個容器(docker stop) 終止一個正在運行的(`Up`)容器變為終止的(`Exit`),指令格式如下 ```shell docker stop [OPTIONS] CONTAINER [CONTAINER...] ``` :::warning 若要終止一個容器也可使用 docker kill。兩者的區別在於 docker stop 會向容器發送 `SIGTERM`,而 docker kill 會向容器發送 `SIGKILL` ::: #### 啟動已終止容器(docker start) 可啟動一個狀態為終止(`Exited`)的容器,指令格式如下 ```shell= docker start [OPTIONS] CONTAINER [CONTAINER...] ``` #### 重新啟動一個容器(docker restart) docker restart 會將容器先 `docker stop` 後再自動執行 `docker start`,指令格式如下。 ```shell= docker restart [OPTIONS] CONTAINER [CONTAINER...] ``` ### 進入調適容器(docker exec/docker attach) 要進入一個正在運行的容器有許多方法,這邊簡介兩種方法 `docker attach` 、`docker exec`,但個人較推薦使用 `docker exec`,下面會解釋原因。 #### docker attach docker attach 指令會開啟一個此容器正在運行的終端,這會有兩個問題。 第一個,若有多個使用者同時 attach 上同一個容器會發現操作會衝突。第二個,由於使用上是與容器同個終端,因此若操作不當可能會不小心將容器關閉,若要離開該終端需要 Ctrl+P 加 Ctrl+Q 。 ```shell docker attach [OPTIONS] CONTAINER ``` #### (*)docker exec 在運行中的容器執行指令可以使用 `docker exec`,後面可以使用多個參數,一樣地,我們可以像在 `docker run` 時為他`-t`分配一個虛擬終端以及將`-i`標準輸出(`STDIN`)保持開啟,指令格式如下。 ```shell sudo docker exec [OPTIONS] CONTAINER COMMAND [ARG...] ``` 若要開啟一個可互動的終端只要在 COMMAND 的部分放上要開啟的 shell 就可以了,以下範例。 ```shell sudo docker exec -it CONTAINER /bin/bash ```  ### 導出與導入容器(docker export/ docker import) #### 導出容器(docker export) 若要將容器導出為某個容器快照文件,可以使用 `docker export`,指令格式如下。 ```shell docker export [OPTIONS] CONTAINER ``` 舉個範例。 ```shell sudo docker export CONTAINER > ubuntuv3.tar ```  #### 導入容器為鏡像(docker import) 若要將由 `docker export` 產生的容器快照文件導入為映像檔,可以使用 `docker import`,指令格式如下 ```shell docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]] ``` 以下範例 ```shell cat [FILENAME] | sudo docker import - [REPOSITORY] ```  :::info 由指令格式可以看到,容器快照文件也支援從網址去導入映像檔 ::: :::warning 在 Image 介紹中也提到了另一個可以導入映像檔的方式 `docker load`。 `docker load` 與 `docker import ` 的區別在於, `docker load` 的鏡像存儲文件會保存完整紀錄, `docker import` 則會丟棄所有歷史紀錄與 metadata。 https://stackoverflow.com/questions/36925261/what-is-the-difference-between-import-and-load-in-docker ::: ### 刪除容器(docker rm) 與刪除映像檔類似,要刪除容器的指令格式如下。 ```shell docker rm [OPTIONS] CONTAINER [CONTAINER...] ``` 相同地,若要強制刪除一個容器可以加上 `-f`,會發送 `SIGKILL` 給該容器。 ```shell docker rm -f CONTAINER [CONTAINER...] ``` docker 提供了一個指令可以清除所有終止狀態(`exited`)的容器,指令格式如下。 ``` docker container prune ``` ### 容器數據管理 容器實例是由映像檔加上一層可讀可寫層構成,文件的新增與修改都在這上面。這會有以下問題。 1. 當該容器不再存在時,數據不會持久存在,並且如果另一個進程需要數據,則很難將數據從容器中取出。 2. 容器的可寫層與運行容器的主機緊密耦合。您無法輕鬆地將數據移動到其他地方。 3. 寫入容器的可寫層需要存儲驅動程序來管理文件系統。存儲驅動程序提供了一個聯合文件系統,使用 Linux 內核。與使用直接寫入主機文件系統的數據卷相比,這種額外的抽象會降低性能。 from https://docs.docker.com/storage/ 解決這個數據管理的問題,有三種解決方法,`bind mount`、`volumes`、`tmpfs mount`,這邊會簡介前兩個方法,`tmpfs mount` 會將檔案儲存於 Memory 當中,因此並不持久化,常用在 Linux 系統上的 docker 與不希望數據在主機或容器內持久存在的情況,有興趣的人能到這裡[查看](https://docs.docker.com/storage/tmpfs/)相關資訊。  #### bind mount bind mount 是一個 docker 早期便可使用的功能,使用 bind mount 會將位於本機上的指定檔案或目錄讓容器掛載。 bind mount a,但因可以通過容器中運行的進程來更改主機文件系統,所以不當使用時可能帶來資安問題,若要指定一個容器 `bind monut` ,需要在其實例化的時候 `docker run` 給予參數 `-v`,指令格式如下。 ```shell sudo docker run -dit -v host/path/to/mount:container/path/to/mount [CONTAINER] ``` **host/path/to/mount**:宿主機上的路徑 **container/path/to/mount**:容器上的路徑 #### volumes 在使用 volume 時,docker 會將 volume 存放在 Docker area,以 Linux 來說預設是 `/var/lib/docker/volumes`,**非 Docker 的行程不應該修改檔案系統中的這一部分**,可使用 `docker volume ls` 來確認當前有哪些 Volume。 首先,我們需要創建一個 Volume,我們需要使用 `docker volume create` 來創建一個 Volume,指令格式如下。 `docker volume create [OPTIONS] [VOLUME]` :::info 若在創建容器實例(`docker run`)的過程中,指定了尚未創建(`docker volume create`) 的容器,將會自動創建一個該 Volume。 ::: 再來,與 `bind mount` 類似地,我們將容器使用 Volume 需要在創建容器的過程中給定 `-v` 參數,與 `bind mount` 相同,不過這次冒號左方不是放路徑,而是 Volume 的名稱(可以透過 `docker volume ls` 來獲得),指令格式如下 ```shell sudo docker run -dit -v volume_name:container/path/to/mount [CONTAINER] ``` 創建完成後,我們可以透過 `docker inspect [CONTAINER] | grep volume` 來查看放置 Volume 的路徑。 在一般情況下,我們刪除容器不會連同 Volume 一同刪除,但若要連同 Volume 一併刪除,可以在 `docker rm` 時,給定 `-v` 的參數。 若要刪除 Volume ,可以使用 `docker volume rm [VOLUME]`。使用 `docker volume prune` 可刪除無主的 Volume。 #### bind mount vs volumes 以下分別條列使用 bind mount 及 volume 的時機,若沒有特別需求,較推薦使用 volume - bind mount 1. 將 config 同時分享給容器與宿主機時。容器預設的 DNS 解析處理便是透過掛載 `/etc/resolv.conf` 分享設定檔到各容器下。 2. 在宿主機以及容器這兩個開發環境分享原始碼或檔案時。一般情況下 docker 內沒有編輯器。 3. 確保掛載路徑的子路徑與檔案穩定。 - volume 1. 在多個容器中共用資料時。創建容器實例時可使用 `--volumes-from` 複製另一容器的 volume 設定。 2. 需要備份與遷移數據的時候。docker 有一許多指定能做到這件事,只需對 docker area 進行操作即可,預設為 ` /var/lib/docker/volumes/` ::: success bind mount 與 volumes 的另一點不同在於 volumes 是由 Docker 創建的,和宿主機的核心(core functionality)隔離。 掛載 bind mount 與 volume 除了 `-v` 也可以透過 `--mount` ,`-v` 較為直觀,相對的 `--mount` 能指定更多東西,有興趣的人能到[這裡](https://docs.docker.com/storage/volumes/#choose-the--v-or---mount-flag)研究。 ::: ## Dockerfile ### .dockerignore 與 Git 中的 `.gitignore` 類似,可將規則寫入 `.gitignore` 中,就會以黑名單的方式防止檔案複製入容器中。 例如我們在 Dockerfile 中撰寫 `COPY . .`,我們可以寫以下 `.gitignore` 將不想複製入容器的檔案隔離在外。 ``` Dockerfile* .git **/.git **/node_modules ``` `**` 任意數量路徑匹配(包含 0 路徑) ### 其餘指令 #### WORKDIR 可設置在 `docker build` 時的工作目錄,之後的指令(`RUN`、`CMD`、`ENTRYPOINT`、`COPY`、`ADD`)皆會在該目錄下進行。 指令格式如下。 ``` WORKDIR /path/to/workdir ``` #### ENV 可設置環境變數。指定格式如下。 ``` ENV <key>=<value> ... ``` #### ARG 與 `ENV` 類似可設置環境變數,不過只能作用於 `docker build` 的過程中。指定格式如下。 ``` ARG <name>[=<default value>] ``` #### VOLUME 在 Dockerfile 中,可以透過`VOLUME`將容器內路徑掛載至自動生成的 Volume,在創建容器實例時,可以透過 `docker run -v` 將其掛載點覆蓋。指令格式如下。 ``` VOLUME ["</data1>", "</data2>"...] VOLUME </data1> ``` :::info 透過 Dockerfile 指定的 VOLUME 使透過此方式產生的映像檔創建的容器實例都有了掛載點。 與在 `docker run -v` 不同在於,Dockerfile 指定的 VOLUME 不能指定宿主機掛載點與 Volume name。 ::: #### USER 可設置在運行映像時的使用者名稱或 UID ,為之後的指令操作(`RUN`、`CMD`、`ENTRYPOINT`)設置權限。指令格式如下。 ``` USER <user>[:<group>] USER <UID>[:<GID>] ``` #### ENTRYPOINT 設置容器啟動後執行的命令,不會被 `docker run` 提供的參數覆蓋,如果在 Dockerfile 中有多個 `ENTRYPOINT`,則只會認最後一個。指令格式如下 ``` ENTRYPOINT ["executable", "param1", "param2"] ENTRYPOINT command param1 param2 ``` :::info CMD 與 ENTRYPOINT 的差異簡而言之就是, CMD 會被 `docker run` 提供的參數覆蓋, ENTRYPOINT 不會被覆蓋。 兩者同時使用時,結果會如在 shell 中執行 <ENTRYPOINT> "<CMD>" ::: ### 網路調適(EXPOSE) 若有一個客戶端想要請求容器內的一個服務,如下圖。  在沒有設定網路的狀況下,我們無法存取容器內的服務,因為該服務被保護於 Server 中,若想要開放外部存取該服務,常見可使用端口映射。將容器中的 Port 對應到 Server 中的 Port,這樣外部只要存取 Server 宿主機上的 Port ,便可連線到容器中的服務。 在 Dockerfile 中,`EXPOSE` 可說明容器打算將什么端口向外暴露,可使此映像的使用者理解此映像的服務端口,方便後續配置映射;另外也可在使用 `docker run -P` 時能預設指定該 `EXPOSE` 的端口映射隨機宿主機端口。 若想將宿主機端口映射至容器中除了上方提到的 `docker run -P` 外,還有許多做法,以下簡介 `docker run -P` 用法與 `docker run -p HostPort:ContainerPort`。 可使用 `docker port [CONTAINER]` 來檢視容器端口映射狀況 #### docker run -P 可以在 `docker run` 時使用 `-P` 或 `--publish-all=true`,會根據容器指定的 `EXPORT` 端口(除了在 Dockerfile 中指定也可透過 `--expose [PORT]` 指定) 隨機分配一個尚未使用的端口映射至容器服務端口,可至 `/proc/sys/net/ipv4/ip_local_port_range` 查看分配範圍。 指令格式如下。 ```shell sudo docker run -P IMAGE [COMMAND] [ARG...] ``` #### docker run -p 除了將所有 EXPOSE 的端口映射至隨機宿主機端口,也可透過 `docker run -p` 來選擇性的設置宿主機與容器內端口的映射。如果要將容器內的 `80` 端口映射至宿主機的 `8080` 端口,可在 `docker run` 時加入 `-p 80:8080` 或 `--publish=80:8080` 來指定端口映射。 指令格式如下 ```shell sudo docker run -p HOST_PORT:CONTAINER_PORT [IMAGE] ``` docker 的 port forwarding 預設會將 0.0.0.0、::: 上的端口映射出去,也就是所有符合的 ipv4、ipv6。docker 也支援綁定宿主機上的 IP。(可綁定 127.0.0.1 使該服務僅供本機存取) 指定格式如下 ```shell sudo docker run -p IP:HOST_PORT:CONTAINER_PORT [IMAGE] ``` 此外,該指令若不給予 `HOST_PORT` ,則會與 `docker run -P` 類似,隨機分配一個宿主機端口映射服務。 指令格式如下。 ```shell sudo docker run -p CONTAINER_PORT [IMAGE] ``` :::info docker run -p 有許多映射方式,可自行嘗試。 `docker run -p [[[IP:]HOST_PORT:]CONTAINER_PORT]` ::: :::warning 若端口映射關係複雜,建議使用 docker-compose 此工具處理。 ::: ### 映像檔轉換為 Dockerfile 若有將映像檔轉換為 Dockerfile,可使用[這個](https://github.com/CenturyLinkLabs/dockerfile-from-image)逆向工程工具。 ### Docker layer ``` FROM ubuntu:15.04 MAINTAINER Vincent WORKDIR /app/ RUN echo "echo 'hello'" > sayhello.sh ENTRYPOINT ["sh", "sayhello.sh"] ```  ``` docker history ``` docker 預設最多支援 127 層,層與層之間透過[聯合檔案系統](https://en.wikipedia.org/wiki/UnionFS)支援把多個目錄內容聯合掛載到同個虛擬檔案系統下,若遇到了有同個路徑,則上層路徑會覆蓋下層路徑。  docker 中,映像檔使用了分層的結構,透過這樣的結構,能很好的進行**共享資源**,例如在構建另一個時,若發現該基底映像存在於本機上,則 docker host 只需在本機保存一份基底映像。 這時可能會出現一個疑問,若有許多映像檔都使用了同一個基底映像檔文件,那當某個容器實例修改了某個基底映像的內容,是否會影響到其他映像檔? 答案是不會的,先前有提到在創建容器實例的時候,會在 Read-only 的 Image layers 上加上一層可讀可寫的 Container layer,所有在容器上的修改皆會被限制在這一層,這運用到了有名的 `copy on write(CoW)` 技巧。 ### copy on write 透過聯合檔案系統技術,在一個多層的映像檔中,每一層的映象層都會一起組成一個完整的文件系統,上層的路徑會覆蓋下層的路徑。 那上面提到,當創建容器實例時,會在只讀的映象層加上可讀可寫的容器層,且所有的修改都在上面進行,那 docker host 是如何透過 `copy on write(CoW)` 將這個讀寫的過程實現呢。以下簡介 CRUD。 1. **新增** : 直接新增在容器層。 2. **讀取** : 由上往下在映像層查找該檔案。 3. **修改** : 由上往下在映像層查找該檔案,找到後複製到容器層,並進行修改。 4. **刪除** : 由上往下在映像層查找該檔案,找到後在容器層記錄刪除的操作。 `copy on write(CoW)` 的主要精神為,只在需要修改時複製該檔案,想知道更細節實作可前往[這裡](https://en.wikipedia.org/wiki/Copy-on-write)。 ## Docker compose docker-compose 是一個能夠透過撰寫 `docker-compose.yml` 來調適多個容器上的應用的工具。 使用 docker-compose 主要有三個步驟。 1. 設定你的應用們的 `Dockerfile`,並確保它們能成功在各處構建服務。 2. 撰寫 `docker-compose.yml`,確保各個服務能在獨立環境一起運行。 3. 使用 `docker-compose up`,開始運行整個服務。 一個 docker-compose.yml 看起來像是。 ```yaml= version: '3.1' services: db: image: mysql command: --default-authentication-plugin=mysql_native_password restart: always environment: MYSQL_ROOT_PASSWORD: example adminer: image: adminer restart: always ports: - 8080:8080 ``` **line 1** Compose file 的版本 **line 2** 開始設置各個服務 **line 3、9** 各個服務的名稱 **line 4、10** 此服務使用的現有映像檔 **line 5** 容器啟動後要執行甚麼指令 **line 6、11** 容器重新啟動規範,可使用no、always、on-failure、unless-stopped,設定 always 表示容器停止或關機都會自動啟動容器 **line 7** 定義容器內的環境變數 **line 12**與 `docker run -p` 類似,冒號左方為宿主機端口,右方為容器內端口 https://hub.docker.com/_/mysql/ ### docker-compose 常用指令 - docker-compose ps - 查看容器狀態 - docker-compose stop - 停止容器,可使用 `docker-compose start` 重新啟動 - docker-compose start - 啟動容器 - docker-compose down - 停止並移除Network與容器 - `-v` 移除 Volume - docker-compose up - 建立並執行Network與容器 - `-d` 背景執行 - `--build` 會再 build 一次映像 - docker-compose logs - 查看服務輸出的日誌 - `-f` 持續追蹤日誌 - docker-compose rm - 刪除已停止的容器 - `-v` 連同 volume 一起刪除 - docker-compose exec - `docker-compose exec [options] [-e KEY=VAL...] SERVICE COMMAND [ARGS...]` - 進入容器內執行相關指令與操作 ### docker-compose.yml 架構 #### links - 將兩個服務連結起來,可直接使用 services 作為 hostname。 - 範例 ```yaml ... web: links: - "db" - "db:database" - "redis" ... ``` 可使用 docker-compose exec 進入容器並試試看是否能 ping 到該服務 ``` sudo apt-get update sudo apt-get install iputils-ping ``` #### depends_on - 設定該服務會在給定的服務照順序開始配置,依照以下情況。 - `docker-compose up` 給定的服務(db、redis)會先建置完之後再建置該服務(web)。 - `docker-compose up SERVICE` 會自動先將給定的服務(db、redis)建置完,再建構該服務(web)。 - `docker-compose stop` 會先將該服務(web)停止,之後再將給定的服務(db、redis)停止。 ```yaml version: "3.9" services: web: build: . depends_on: - db - redis redis: image: redis db: image: postgres ``` ### cheatsheet https://devhints.io/docker-compose ## 結尾
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.