# 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 進行了進一步的封裝 - 可將應用程式與其環境打包,使其移植十分方便 ![](https://i.imgur.com/1hFJgKK.png) ### 若遇到了以下的情境 ![](https://i.imgur.com/3sRaoje.png) #### 解決方案 1. Virtual environment 2. Virtual machine 3. Docker #### Virtual environment > 有些情況不能純使用虛擬環境,此方法不通用(跨平台) ![](https://i.imgur.com/KUwulTo.png) #### 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 ![](https://i.imgur.com/rnh9Hw6.png) 虛擬機需要虛擬出完整的機器,包括 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基本概念 ![](https://i.imgur.com/Fe97hJn.png) ### Docker images Docker 映像檔為一個唯獨的模板,包含了構建 Docker Container 的必要資訊,Docker image 可以是多個 Docker image 堆疊而成,如下圖。 ![](https://imgur.com/QIp9gOX.png) 構建出 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 衝突的問題。 ![](https://i.imgur.com/QqwxwZJ.png) ### Docker images vs Container ![](https://i.imgur.com/AEtRqIi.png) ### Docker registry registry 是集中存放映像檔的地方,中文稱作倉庫,倉庫又區分為公有倉庫與私有倉庫。 - 公有倉庫 其中公有倉庫最常使用的為 Docker 官方維護的 [Docker Hub](https://hub.docker.com/),大部分我們需要的現成映像檔都會從這去拉取,我們可以透過 `docker search [OPTIONS] TERM`去搜尋官方倉庫中的映像檔。 ![](https://imgur.com/XvCgAd3.png) 可以使用 `--filter=stars=[startnumber]` 來限定只會出現幾顆星星以上的映像檔。 ![](https://imgur.com/QwkiUUM.png) 登入後可用 `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` ![](https://i.imgur.com/HcVvqA5.png) ## Docker Image ### 拉取現有映像檔(docker pull) 從公開或私有的倉庫拉取 image,預設是 Docker Hub 公共註冊伺服器中的 Repository。 ![](https://imgur.com/B7Cbq5K.png) ### 檢視當前映像檔(docker images) 檢視當前存在的 docker image,是 `docker image list` 的別名 ![](https://imgur.com/DGceXjX.png) ### 修改已有映像檔(docker commit) 若想修改已有映像檔,則需使用`docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]` 我們可以先製作一個映像檔實例,也就是容器,並在該目錄創建一個隨意檔案。 ![](https://i.imgur.com/irhZpLO.png) 完成修改後,我們退出 Docker 介面,之後在 Host 下 `sudo docker commit -m "change file" 4ea57b5196be ubuntu:v2` ,其中 f4fd69e6adad 為經修改之 Container ID,ubuntu 及 v2 分別為 REPOSITORY 與 TAG。之後確認 `sudo docker images`,發現新出現了一個 docker image(畫面中的第一個)。 ![](https://i.imgur.com/9exag4p.png) 並用該 docker image 重新創建一個容器實例,發現是經過修改後的結果。 ![](https://i.imgur.com/DmLK0Zi.png) ### 自行建立並設計映像檔(Dockerfile/docker build) 對於小型的個人專案,透過 `docker commit` 來擴展一個 Docker Image 還算是一個方法,但若場景換到多人合作與大型專案需要進行分享就不適合。 Docker 提供了簡單的方式來自定義一個 image,我們只要寫好 Dockerfile 後,執行 `docker build` 便可自動化的配置好我們的 Docker image。Dockerfile 內每一行皆是用來建立映像檔的指令(Instruction)。 我們準備好一個空目錄,並在裡面放入一個包含 `print('Hello Docker!')` 名為 `hello.py`的 python 文件。 ![](https://i.imgur.com/G3izqDu.png) 再來,我們在該路徑下新增包含以下內容的 Dockerfile ![](https://i.imgur.com/4aCLWbH.png) ```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 ::: 執行結果如下。 ![](https://i.imgur.com/UWWq1pP.png) 我們檢查 `docker images`,並用 docker run 創建容器實例,看是否會印出東西。 ![](https://i.imgur.com/wPmlo6g.png) ### 移除映像檔(docker rm) 若要移除某個 images 可以使用以下指令。若要強制刪除 image 可以加上 `-f` 來 force remove。 ```shell docker rmi [OPTION] [IMAGE ID] ``` ![](https://i.imgur.com/kYA3Fnz.png) 以下指令能一次強制刪除所有的 images ```shell docker rmi -f $(docker images -aq) ``` ### 匯出與匯入映像檔(docker save/ docker load) #### docker save ```shell sudo docker save -o FILENAME IMAGE [IMAGE...] ``` ![](https://i.imgur.com/B3FUdEx.png) #### docker load 載入匯出後的 images 檔案。 ```shell sudo docker load -i [FILENAME] ``` ![](https://i.imgur.com/GGSXt0P.png) ## 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'**:指定啟動後需要運行什麼指令 ![](https://i.imgur.com/xZGP4dy.png) 以下簡列 docker run 後的步驟 1. 檢查本地是否有該映像檔,否則自動使用 `git pull` 2. 利用該映像檔創建並啟動一個容器 3. 分配一個 file system,並在映像檔外掛載一個可讀寫層 4. 從宿主主機上網路橋接口橋接一個到該容器內 5. 分配一個 ip 給該容器 6. 執行指定的應用程式 7. 執行完畢後容器終止(若沒特別指定 Daemonized) 在很多時候,我們會想要進入容器互動,因此我們可以為容器分配一個虛擬終端(`-t`)並讓標準輸入保持打開(`-i`),這兩個參數可擺在同個 - 後面(`-it`)。 ![](https://i.imgur.com/OniMk8P.png) 若想在容器主程序一結束時,立刻移除容器可使用 `--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" ``` ![](https://i.imgur.com/HIWllOc.png) :::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 ``` ![](https://i.imgur.com/1b21Cfq.png) ### 導出與導入容器(docker export/ docker import) #### 導出容器(docker export) 若要將容器導出為某個容器快照文件,可以使用 `docker export`,指令格式如下。 ```shell docker export [OPTIONS] CONTAINER ``` 舉個範例。 ```shell sudo docker export CONTAINER > ubuntuv3.tar ``` ![](https://i.imgur.com/X7iYdYo.png) #### 導入容器為鏡像(docker import) 若要將由 `docker export` 產生的容器快照文件導入為映像檔,可以使用 `docker import`,指令格式如下 ```shell docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]] ``` 以下範例 ```shell cat [FILENAME] | sudo docker import - [REPOSITORY] ``` ![](https://i.imgur.com/nPIzNSW.png) :::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/)相關資訊。 ![](https://i.imgur.com/WvnLZ3y.png) #### 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) 若有一個客戶端想要請求容器內的一個服務,如下圖。 ![](https://i.imgur.com/YCSA5jo.png) 在沒有設定網路的狀況下,我們無法存取容器內的服務,因為該服務被保護於 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"] ``` ![](https://i.imgur.com/QXRMDTB.png) ``` docker history ``` docker 預設最多支援 127 層,層與層之間透過[聯合檔案系統](https://en.wikipedia.org/wiki/UnionFS)支援把多個目錄內容聯合掛載到同個虛擬檔案系統下,若遇到了有同個路徑,則上層路徑會覆蓋下層路徑。 ![](https://i.imgur.com/bftua03.png) 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 ## 結尾