Docker === ###### tags: `NCNU` `LSA` `Container` `DevOps` ## First ### Install ``sudo apt install docker.io`` ### Check status ``service docker status`` ### Check version ``docker version`` 正常會顯示這樣 ``` Client: Version: 18.09.2 API version: 1.39 Go version: go1.10.4 Git commit: 6247962 Built: Tue Feb 26 23:52:23 2019 OS/Arch: linux/amd64 Experimental: false Server: Engine: Version: 18.09.2 API version: 1.39 (minimum version 1.12) Go version: go1.10.4 Git commit: 6247962 Built: Wed Feb 13 00:24:14 2019 OS/Arch: linux/amd64 Experimental: false ``` 若有出現什麼錯誤或是權限問題 需將帳號加入 docker 群組並登出/重起 ``sudo usermod -aG docker yourAccount`` ### Search Image 舉例搜尋 hello-world ``docker search hello-world`` ``` NAME DESCRIPTION STARS OFFICIAL AUTOMATED hello-world Hello World! (an example of minimal Dockeriz… 871 [OK] kitematic/hello-world-nginx A light-weight nginx container that demonstr… 124 tutum/hello-world Image to test docker deployments. Has Apache… 59 [OK] dockercloud/hello-world Hello World! 15 [OK] hypriot/armhf-hello-world Hello World! (an example of minimal Dockeriz… 6 ... ``` 選好之後下載 ``docker image pull hello-world`` :::info 可以不加 image 但現在版本對指令有規範 故養成好習慣 以下 image 指令皆不再贅述 ::: ### View all downloaded Images ``docker image ls`` :::info 也可以用 ``docker images`` ::: ``` REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest fce289e99eb9 3 months ago 1.84kB ``` ### Run Image ``docker container run hello-world`` :::info 可以不加 container 但現在版本對指令有規範 故養成好習慣 以下 container 指令皆不再贅述 ::: ### Delete Image - ``docker image rm {Image Name}:{Tag}`` - ``docker image rm {Image ID}`` :::info image rm 可寫成 rmi ::: ex: ``docker rmi hello-world:latest`` ``docker rmi fce289e99eb9`` 刪除所有 image ``docker image prune`` #### 如果 image 已經在 container 中被使用 1. 查看所有 container ``docker container ls [-a]``(預設只顯示執行中的) 也可以用 ``docker ps [-a]`` ``` CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4fc65c601e5a hbdoy/node-web-app:1.0 "npm start" 6 minutes ago Exited (0) 3 minutes ago zealous_feistel 4a7891a432a2 hello-world "/hello" 5 hours ago Exited (0) 5 hours ago sad_beaver ``` 2. 終止該 container ``docker container stop {CONTAINER ID}`` 3. 移除該 container ``docker container rm {CONTAINER ID}`` 4. 刪除 image ``docker rmi {Image ID}`` ### Other Container Command #### create ``docker container create {Image} [--name xxx]`` ``docker container create {Image} sh -c "while true; do sleep 3600; done"`` #### start ``docker container start {Container ID}`` #### create & start ``docker container run {Image} [-d]`` :::info ``-d`` 讓 container 在背景執行 ::: #### filter ``docker container ls [-q] -f status=created`` :::info ``-q`` 只會列出 Container ID ::: 批次啟動 ``docker container start $(docker container ls -q -f status=created)`` 批次刪除 ``docker container rm $(docker container ls -a -q)`` ``docker container prune`` 查看 container IP ``docker inspect <container ID> | grep IPAddress`` ### Commit 若是 image 內容有更新,可以重新打包成一個新的 image ``docker commit {container ID} {newName}[:tag] `` ex: 在 ubuntu image 安裝 nginx ``` docker run -d -p 80:80 -it ubuntu /bin/bash apt-get update apt-get install nginx service nginx start ps aux exit ``` ``` docker commit {container id} ubuntu_nginx ``` ## Docker Machine 方便快速的建立 docker host (擁有 docker 環境) 若有多台虛擬機則不需要手動逐一安裝 docker ### Install ``` base=https://github.com/docker/machine/releases/download/v0.16.0 && curl -L $base/docker-machine-$(uname -s)-$(uname -m) >/tmp/docker-machine && sudo install /tmp/docker-machine /usr/local/bin/docker-machine ``` ### Check version ``docker-machine version`` ### Create :::info 請事先安裝 virtualbox ::: :::danger 若是因為 secure boot 導致 virtualbox 無法使用可以參考: [VirtualBox + Secure Boot + Ubuntu = fail](https://stegard.net/2016/10/virtualbox-secure-boot-ubuntu-fail/) ::: ``docker-machine create demo`` 預設的 driver 會是 virtualbox 如果需要更改可以加上參數 ``-d "xxx"`` 或是直接輸入 ``docker-machine create`` 查看更多說明 #### 查看建立的 machine ``docker-machine ls`` #### 常用指令 ``docker-machine start demo`` ``docker-machine stop demo`` ``docker-machine rm demo`` ``docker-machine scp my-file.txt demo:~`` ### SSH ``docker-machine ssh demo`` ## Play with Docker 只是想要試試 Docker 但覺得安裝太麻煩了嘛 可以試試 [Play with Docker](https://labs.play-with-docker.com/) 註冊後就可以得到一個臨時的 Docker Host(4小時就會重置一次) 可以透過 web terminal 或 ssh 進去使用 ## Docker Hub [Docker Hub](https://hub.docker.com/) 像是 Github 只是裡面存放 docker images 可以上去找自己需要的 images 也可以把自己的 images 放上去 ### Make your own Image 此處以 nodejs 為例 1. 建立最基本的 express 範例 ``` $ npm init $ npm install express --save ``` **index.js** ```javascript= var express = require("express"); var app = express(); app.get('/', function (req, res) { res.send('Hello World!'); }); app.listen(3000, function () { console.log('Example app listening on port 3000!'); }); ``` **package.json** ``` { "name": "demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node index.js" }, "author": "", "license": "ISC", "dependencies": { "express": "^4.16.4" } } ``` 2.在相同目錄下建立 ``Dockerfile`` 檔案 ``` FROM node:8 # Create app directory WORKDIR /usr/src/app # Install app dependencies COPY package*.json ./ RUN npm install # Bundle app source COPY . . # port EXPOSE 3000 CMD [ "npm", "start" ] ``` 以及 ``.dockerignore`` ``` node_modules npm-debug.log ``` 3. Build ``` docker build -t {name}:{tag} . ``` ex: 為了方便推上 Dockerhub,name 可以取 Dockerhub 帳戶名稱即可 ``` docker build -t hbdoy/node-web-app:1.0 . ``` 4. 查看是否建立完成 ``docker images`` ``` REPOSITORY TAG IMAGE ID CREATED SIZE hbdoy/node-web-app 1.0 25960f3be895 7 minutes ago 898MB node 8 8c51cec97ebf 9 days ago 895MB ``` 5. 執行 Image ``docker run -p 49160:3000 -d hbdoy/node-web-app:1.0`` :::info ``-p`` 映射 port ::: 6. 查看 console ``docker ps`` ``` CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f2c21b27a12d hbdoy/node-web-app:1.0 "npm start" 10 seconds ago Up 8 seconds 0.0.0.0:49160->3000/tcp agitated_swirles ``` ``docker logs {CONTAINER ID}`` ``` > demo@1.0.0 start /usr/src/app > node index.js > Example app listening on port 3000! ``` 接著查看 ``localhost:49160`` ~ 7. 也可以進入 container 中 ``docker exec -it {Container ID} /bin/bash`` ``` root@05dd08c41e21:/usr/src/app# ls Dockerfile index.js node_modules package-lock.json package.json ``` 8. 將 Image 上傳 沒有登入過的先執行 ``docker login`` 接著 ``docker push {REPOSITORY}:{TAG}`` ex: ``docker push hbdoy/node-web-app:1.0`` :::success 透過 Dockerfile 就能夠方便快速的建立環境,這也是 **Infrastructure as Code** 的理念 ::: ### 改裝現有 Image 覺得 Dockerhub 上的 image 功能不夠多嘛 也可以自己包喔 此處以 ubuntu image 為例 **`Dockerfile`** ``` FROM ubuntu:18.04 # Install Linux library dependency RUN apt-get update RUN apt-get install xxx # Display Greeting CMD [ "echo", "Ubuntu 18.04 LTS with xxx"] ``` 只要有這個 Dockerfile 就能夠建立自訂的 ubuntu container 了,還可以把 Dockerfile 列入 git 版控 ## Container Network Model(CNM) ### Network Namespace 每個 container 都有自己的 network interface、routing table、Firewall rules ![](https://i.imgur.com/WPYC55F.png) ### CNM Drivers ![](https://i.imgur.com/07EXg7C.jpg) Container 在單個 Docker Host 的通訊方式 1. Linux Bridge 2. IPtables Container 在多個 Docker Hosts 的通訊方式 1. Tunnel - Docker build-in overlay network: VXLAN - OVS: VXLAN or GRE - Flannel: VXLAN or UDP - Weave: VXLAN or UDP 3. Routing - Calico: Layer3 routing based on BGP - Contiv: Layer3 routing based on BGP [更多請查看](https://github.com/docker/libnetwork/blob/master/docs/design.md) ## 單一 host 中的兩個 container 此處範例為一個 python flask 建立的 web server,還有用來儲存訪問次數的 redis 資料庫,並將 web server、redis 各別包成 container。 **`app.py`** Q: 在沒有 docker 情況下,redis 的位置可以用環境變數來給予 但當 redis 包成 container 時,該如何知道它的 ip 位址呢? A: 可以透過 bridge 的原理,先啟動 redis 的 container,接著在 build web server 時加入 ``link`` 的參數連結到 redis 的 container。 ```python= from flask import Flask from redis import Redis import os import socket app = Flask(__name__) redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379) @app.route('/') def hello(): redis.incr('hits') return 'Hello Container World! I have been seen %s times and my hostname is %s.\n' % (redis.get('hits'),socket.gethostname()) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True) ``` **`requirements.txt`** ``` flask redis ``` **`Dockerfile`** ``` FROM python:2.7 MAINTAINER hbdoy "s104213046@mail1.ncnu.edu.tw" COPY . /app WORKDIR /app RUN pip install -r requirements.txt EXPOSE 5000 CMD [ "python", "app.py" ] ``` ### Step1 建立 redis container,並將 container 名稱設為 redis ``docker run -d --name redis redis`` 執行後可以查看是否正確運行 ``docker container ls`` ``` CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 81b566229b77 redis "docker-entrypoint.s…" About a minute ago Up About a minute 6379/tcp redis ``` ### Step2 先將 ``app.py`` 中的 host 改成剛剛建立的 redis container 名稱 ```python= redis = Redis(host='redis', port=6379) ``` 接著 build 它 ``docker build -t hbdoy/python-web .`` ### Step3 接下來就可以執行剛剛 build 好的 python-web 了 ``docker images`` ``` REPOSITORY TAG IMAGE ID CREATED SIZE hbdoy/python-web latest 179ea3ad8ed5 2 minutes ago 919MB ``` ``docker run -d --name python-web --link redis -p 5000:5000 179ea3ad8ed5`` ### Step4 查看運行狀況 ``docker container ls`` ``` CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7f383159f5c4 179ea3ad8ed5 "python app.py" 2 minutes ago Up 2 minutes 0.0.0.0:5000->5000/tcp python-web 81b566229b77 redis "docker-entrypoint.s…" 13 minutes ago Up 13 minutes 6379/tcp redis ``` 沒問題就連接看看吧! ``` hbdoy@hbdoy:~$ curl 127.0.0.1:5000 Hello Container World! I have been seen 4 times and my hostname is 7f383159f5c4. hbdoy@hbdoy:~$ curl 127.0.0.1:5000 Hello Container World! I have been seen 5 times and my hostname is 7f383159f5c4. hbdoy@hbdoy:~$ curl 127.0.0.1:5000 Hello Container World! I have been seen 6 times and my hostname is 7f383159f5c4. hbdoy@hbdoy:~$ curl 127.0.0.1:5000 Hello Container World! I have been seen 7 times and my hostname is 7f383159f5c4. hbdoy@hbdoy:~$ curl 127.0.0.1:5000 Hello Container World! I have been seen 8 times and my hostname is 7f383159f5c4. hbdoy@hbdoy:~$ curl 127.0.0.1:5000 Hello Container World! I have been seen 9 times and my hostname is 7f383159f5c4. hbdoy@hbdoy:~$ curl 127.0.0.1:5000 Hello Container World! I have been seen 10 times and my hostname is 7f383159f5c4. ``` ## Docker Volume 當你使用 volume 時,docker 會在你的本機上隨機新增一個資料夾(Local storage area),大部分會在 /var 底下,然後讓這個資料夾跟 container 裡面的某個資料夾互通。 因為他們是互通的,所以當你 container 裡面那個資料夾有任何變更時,本地的資料夾也會跟著變,而且很重要的一點是:container 被刪掉時那個資料夾還會原封不動保留在那邊。 ``` docker volume create --name <volume> # 新增了一個 volume 叫做 db-data docker volume create --name db-data docker volume ls ``` 在啟動時加一個 -v 參數,就可以指定 volume 要跟容器內哪一個資料夾連通 ``` # 查看裡面有什麼檔案 docker run -v db-data:/db/data -it ubuntu ls -l /db/data # 增加一個檔案 test docker run -v db-data:/db/data -it ubuntu touch /db/data/test # 查看裡面有沒有新增檔案 docker run -v db-data:/db/data -it ubuntu ls -l /db/data ``` ## Docker Compose 上面的例子中,只是簡單的計數應用,就有兩個 container。 正常的服務更為複雜,可能需要打包好幾個 services,這幾個 images 在 run 時可能又會有相依與先後問題,管理與執行將會變得很複雜,所以可以用 Docker Compose 來管理。 :::info Docker Compose 為 yaml 格式,定義了 services、network、volumes 等等 ::: service 指的是 container,而它的 image 來源可能是透過 Dockerfile 來 build,或是從 Dockerhub 下載的,或是其他地方。 ### Install 指令中的 **1.24.0** 為版本號(目前最新) ``` sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose ``` ``` sudo chmod +x /usr/local/bin/docker-compose ``` ### DIY 延續上面 python 的例子,在相同目錄下建立 **`docker-compose.yml`** ``` version: "3" services: web: build: . ports: - "80:5000" depends_on: - redis networks: - compose-demo-bridge redis: image: redis ports: ["6379"] networks: - compose-demo-bridge networks: compose-demo-bridge: ``` 執行 build 建立 image ``docker-compose build`` 建立好後執行 ``docker-compose up [-d]``(``-d`` 可以讓 container 在背景執行) :::info 預設抓的設定檔名稱是 **docker-compose.yml** 如果要選擇不同的檔名,則需改成 ``docker-compose -f xxx.yml up [-d]`` ::: 接著到 80 port 看看吧(因為剛剛在 docker-compose 文件中設定的本地 port 是 80) ``` hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 1 times and my hostname is 1c4ae1a99ff3. hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 2 times and my hostname is 1c4ae1a99ff3. hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 3 times and my hostname is 1c4ae1a99ff3. hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 4 times and my hostname is 1c4ae1a99ff3. hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 5 times and my hostname is 1c4ae1a99ff3. ``` 可以查看 container 運行的狀況 ``docker container ls`` ``` CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1c4ae1a99ff3 flask-redis_web "python app.py" About a minute ago Up About a minute 0.0.0.0:80->5000/tcp flask-redis_web_1 fb8d9779af6e redis "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:32768->6379/tcp flask-redis_redis_1 ``` 也可以查看 docker network 狀況 ``docker network ls`` ``` NETWORK ID NAME DRIVER SCOPE cf62ca42e982 bridge bridge local ef1e2d2f865f flask-redis_compose-demo-bridge bridge local 05b99dbed86c host host local 2d9478947980 none null local ``` 想要停止的話就執行 ``docker-compose stop`` :::success 所以我們只需透過 **`docker-compose.yml`** 就省掉了 1.需要先啟動 redis container 2.接著執行 web service 時還需 link 到 redis 、映射 port ::: ### scale 能夠開啟多個 service ``docker-compose scale {Service Name}={數量}`` 這邊結合 load balance 來試試看 ``dockercloud/haproxy`` 提供簡單的 load balance 服務 **`docker-compose.yml`** ``` version: "3" services: web: build: . ports: ["5000"] links: - redis networks: - compose-demo-bridge redis: image: redis ports: ["6379"] networks: - compose-demo-bridge lb: image: dockercloud/haproxy ports: - 80:80 links: - web networks: - compose-demo-bridge volumes: - /var/run/docker.sock:/var/run/docker.sock networks: compose-demo-bridge: ``` 執行 build ``docker-compose build`` 執行 image ``docker-compose up -d`` 查看 container ``docker container ls`` ``` CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3f731c79e055 dockercloud/haproxy "/sbin/tini -- docke…" 53 seconds ago Up 51 seconds 443/tcp, 0.0.0.0:80->80/tcp, 1936/tcp lb-scaling_lb_1 f45f87c70c18 lb-scaling_web "python app.py" 55 seconds ago Up 53 seconds 0.0.0.0:32772->5000/tcp lb-scaling_web_1 b983a50f865e redis "docker-entrypoint.s…" 59 seconds ago Up 55 seconds 0.0.0.0:32771->6379/tcp lb-scaling_redis_1 ``` 連接後可以發現 hostname 都是 **f45f87c70c18**,因為也只有開一個 web service ``` hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 1 times and my hostname is f45f87c70c18. hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 2 times and my hostname is f45f87c70c18. ``` 接下來多開幾個 web service ``docker-compose scale web=3`` 查看一下 container 可以發現最後的 Names 分別有 ``lb-scaling_web_1~3``,也就是目前總共有 3 個 web service ``` CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES eece79e4a00f lb-scaling_web "python app.py" 11 seconds ago Up 7 seconds 0.0.0.0:32774->5000/tcp lb-scaling_web_2 2ee85ec7cd8d lb-scaling_web "python app.py" 11 seconds ago Up 7 seconds 0.0.0.0:32773->5000/tcp lb-scaling_web_3 3f731c79e055 dockercloud/haproxy "/sbin/tini -- docke…" 5 minutes ago Up 5 minutes 443/tcp, 0.0.0.0:80->80/tcp, 1936/tcp lb-scaling_lb_1 f45f87c70c18 lb-scaling_web "python app.py" 5 minutes ago Up 5 minutes 0.0.0.0:32772->5000/tcp lb-scaling_web_1 b983a50f865e redis "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 0.0.0.0:32771->6379/tcp lb-scaling_redis_1 ``` 接下來就連接看看吧(注意 hostname) ``` hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 5 times and my hostname is f45f87c70c18. hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 6 times and my hostname is eece79e4a00f. hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 7 times and my hostname is 2ee85ec7cd8d. hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 8 times and my hostname is f45f87c70c18. hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 9 times and my hostname is eece79e4a00f. hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 10 times and my hostname is 2ee85ec7cd8d. hbdoy@hbdoy:~$ curl 127.0.0.1 Hello Container World! I have been seen 11 times and my hostname is f45f87c70c18. ``` ## Reference [Ubuntu Linux 安裝 Docker 步驟與使用教學](https://blog.gtwang.org/virtualization/ubuntu-linux-install-docker-tutorial/)