# Docker 筆記
###### tags: `Docker`
* 系統需求 : Ubuntu
### 第一部分 : 下載安裝
#### 確保使用HTTPS下載軟件不被竄改
```
# sudo apt-get update
# sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release
```
#### 下載軟件包需要的 GPG 密鑰
```
# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
```
#### 在 sources.list 中添加 Docker 軟件包
```
# echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```
### 第二部分 : 啟動服務
```
//開啟服務
# systemctl start docker
//設定開機啟動
# systemctl enable docker
//停止服務
# systemctl stop docker
//查看服務狀態
# systemctl status docker
```
### 第三部分 : 介紹指令
#### Docker Image
* Docker 映像檔是一個模板,用來重複產生容器實體
1. 列出本地的 image
```
# docker images
# docker image ls
```
2. 刪除 image
```
# docker image rm -f [image id]
# docker rmi [image id]
```
3. 替 image 加上標籤/別名
```
# docker tag [source] [target]
```
4. 建立容器但沒有執行
```
# docker create -it [image] --name [container]
```
5. 將容器轉換為 image
```
# docker commit [container id] [new image name]
```
6. 查看映像檔的詳細資料
```
# docker inspect [image]
```
7. 執行 A 專案的 docker image(B的版本)
```
# docker run A:B`
```
8. 看看這個 image 的磁碟空間
```
# docker run A df
```
#### Docker Container
* 容器是用映像檔建立出來的執行實例,它可以被啟動、開始、停止、刪除。每個容器都是相互隔離、保證安全的平台。
1. 列出正在運行的contianer
```
docker ps
```
2. 列出所有的 Container
```
# docker ps -a
```
3. 用 `<CONTAINER ID>` 刪除 Container
```
# docker rm -f <CONTAINER ID>
# docker pull simonwxzhao/auto-test
```
4. 建立 Container
```
# docker create -i -t --name [container] [image name]
# docker create -i -t --name foo docker-test
3756e5fc76f82e66cc10e3aca0d111bccaa61a62b41810c85254afff725c67f0
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3756e5fc76f8 docker-test "/docker-entrypoint.…" 7 seconds ago Created foo
```
5. 執行容器
```
# docker start -i [container]
```
6. 離開容器 (`ctrl+p+q`) 後再進入
```
# docker attach [container]
```
7. 停止容器
```
# docker stop [container]
```
8. 刪除容器
```
# docker rm [container]
```
9. 刪除全部容器
```
# docker rm $(docker ps -aq)
# docker rm -f `docker ps -aq`
```
#### Registry (Docker Hub)
* 倉庫(Repository)是集中存放映像檔檔案的場所
1. 登入 Registry
```
# docker login
```
2. 經由8080進入倉庫
```
# docker login localhost:8080
```
3. 退出 Registry
```
# dokcer logout
```
4. 抓取A專案最新的版本
```
# docker pull A:latest
```
5. 把映像檔推到倉庫(docker hub)
```
# docker push [image]:[tag]
```
##### 查看 Images 的紀錄
```
# docker run -d -v /home/user/volume-demo:/usr/share/nginx/html --name vol-example nginx:latest
97cb5f09456c63a2d117ef035fade334869d980141e696195fdc5765d9e49e53
# docker run -d -v $PWD:/usr/share/nginx/html --name vol-example nginx:latest
# docker run -d -v $(pwd):/usr/share/nginx/html --name vol-example nginx:latest
```
### 第四部分 : DOCKER IMAGE PUSH/PULL ON DOCKER HUB
#### 1. 登入 Docker Hub 帳號
```
# docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: yichien1019
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
```
#### 2. 修改 IMAGE 名稱 : `docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]`
```
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-test latest e51d91cc64cd 3 hours ago 22.3MB
# docker tag docker-test:latest yichien/docker-test0.1
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-test latest e51d91cc64cd 3 hours ago 22.3MB
yichien/docker-test0.1 latest e51d91cc64cd 3 hours ago 22.3MB
```
#### 3. 回推 IMAGE
```
# docker push yichien1019/docker-test0.1
Using default tag: latest
The push refers to repository [docker.io/yichien1019/docker-test0.1]
d9d86e7e46f7: Pushed
3633e038dbe3: Pushed
e8f8cd3583be: Pushed
0614f8d14b89: Pushed
029c325415ee: Pushed
777b2c648970: Pushed
latest: digest: sha256:33d6c64f7198c74f49702d74cbdf1012e136864f2cbebf7c9fd828fff9b0187f size: 1567
```
##### DOCKER HUB

#### 4. 刪除鏡像
* 刪除鏡像前,如果有容器正在使用,要先把容器刪除,才能再刪除鏡像
```
# docker rmi yichien1019/docker-test0.1
Untagged: yichien1019/docker-test0.1:latest
Untagged: yichien1019/docker-test0.1@sha256:33d6c64f7198c74f49702d74cbdf1012e136864f2cbebf7c9fd828fff9b0187f
```
#### 5. 測試 PULL IMAGE
```
# docker pull yichien1019/docker-test0.1
Using default tag: latest
latest: Pulling from yichien1019/docker-test0.1
Digest: sha256:33d6c64f7198c74f49702d74cbdf1012e136864f2cbebf7c9fd828fff9b0187f
Status: Downloaded newer image for yichien1019/docker-test0.1:latest
docker.io/yichien1019/docker-test0.1:latest
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
webserver-test2.0 latest b2cf4dcf3367 About an hour ago 22.3MB
docker-test latest e51d91cc64cd 4 hours ago 22.3MB
yichien1019/docker-test0.1 latest e51d91cc64cd 4 hours ago 22.3MB
myhttpd 0.1 063c01b6b66d 4 hours ago 145MB
nginx latest 5d58c024174d 4 days ago 142MB
httpd 2.4 d16a51d08814 2 weeks ago 145MB
centos 7 eeb6ee3f44bd 13 months ago 204MB
nginx 1.19.6-alpine 629df02b47c8 22 months ago 22.3MB
```
### 第五部分 : DOCKER IMAGE PUSH/PULL ON AWS ECR
#### 1. 先在 AWS ECR 上創建儲存庫
* 可以選擇 Public(公有) 或 Private(私有)
#### 2. 設定 AWS Cli
```
# aws configure
AWS Access Key ID [****************EMP7]: [存取金鑰 ID]
AWS Secret Access Key [****************wT0D]: [私密存取金鑰]
Default region name [ap-northeast-1]: [AWS 區域]
Default output format [json]: [輸出格式]
# aws configservice put-aggregation-authorization --authorized-account-id 285167715064 --authorized-aws-region ap-northeast-1
{
"AggregationAuthorization": {
"AggregationAuthorizationArn": "arn:aws:config:ap-northeast-1:285167715064:aggregation-authorization/285167715064/ap-northeast-1",
"AuthorizedAccountId": "285167715064",
"AuthorizedAwsRegion": "ap-northeast-1",
"CreationTime": "2022-08-01T09:59:02.916000+08:00"
}
}
```
#### 3. 擷取驗證字符並將 Docker 用戶端驗證至您的登錄檔
```
# aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 285167715064.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded
```
#### 4. 建置 Docker 映像
```
# docker build -t eva-test .
[+] Building 0.1s (9/9) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 31B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/nginx:latest 0.0s
=> [1/4] FROM docker.io/library/nginx 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 56B 0.0s
=> CACHED [2/4] WORKDIR /app 0.0s
=> CACHED [3/4] COPY index.html /usr/share/nginx/html 0.0s
=> CACHED [4/4] COPY nginx.conf /etc/nginx/conf.d/default.conf 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:2e3da95a84aa6f7a756b8d4751f2f1655df8b08c2285b159ee8eba00adab9118 0.0s
=> => naming to docker.io/library/eva-test
```
#### 5. 建置完成後加上標籤,以便將映像推送至此儲存庫
```
# docker tag eva-test:latest 285167715064.dkr.ecr.ap-northeast-1.amazonaws.com/eva-test:latest
```
#### 6. 將此映像推送至 AWS 儲存庫
```
# docker push 285167715064.dkr.ecr.ap-northeast-1.amazonaws.com/eva-test:latest
The push refers to repository [285167715064.dkr.ecr.ap-northeast-1.amazonaws.com/eva-test]
2e2a82515826: Pushed
083c1a49f16a: Pushed
e5862212052f: Pushed
abc66ad258e9: Pushed
243243243ee2: Pushed
f931b78377da: Pushed
d7783033d823: Pushed
4553dc754574: Pushed
43b3c4e3001c: Pushed
latest: digest: sha256:dea4b2ec40ab9d2d22e3839c94bb1c9f1de980fdd97ce9b589d57d43bde11033 size: 2191
```
#### AWS ECR

### 第六部分 : DOCKER COMMIT
* 從容器創建一個新鏡像
| 參數 | 內容 |
|------|------|
| -a | --author string 作者 |
| -c | --change list 對創建的鏡像應用 Dockerfile 指令 |
| -m | --message 提交時的說明文字 |
| -p | --pause 在commit時,將容器暫停 |
#### Example. `docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]`
```
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5371d449897d webserver-test "/docker-entrypoint.…" 2 hours ago Up 2 hours 0.0.0.0:8080->80/tcp, :::8080->80/tcp heuristic_diffie
# docker commit 537 webserver-test2.0
sha256:b2cf4dcf3367c2c35d37aad4c822006b0ec282056ddfd2b28007d65e8697b9d3
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
webserver-test2.0 latest b2cf4dcf3367 50 seconds ago 22.3MB
webserver-test latest e51d91cc64cd 2 hours ago 22.3MB
```
### 第七部分 : Docker Volume
* Docker 容器的資料儲存(storage)問題
* 使用 bind mount 的 volume 其實就是透過 host machine 的檔案系統(filesystem)提供容器儲存的能力。
#### 1. 新增 volume
```
# docker volume create [volume name]
# docker volume create my-volume
my-volume
```
#### 2. 列出所有 volumes
```
# docker volume ls
DRIVER VOLUME NAME
local my-volume
```
#### 3. 列出 volume 詳細資訊
```
# docker volume inspect [volume name]
# docker volume inspect my-volume
[
{
"CreatedAt": "2022-10-24T23:14:27-07:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-volume/_data",
"Name": "my-volume",
"Options": {},
"Scope": "local"
}
]
```
#### 4. 共享儲存空間
```
# docker run -d -p 8080:80 --name volume1 -v my-volume:/usr/share/nginx/html nginx:latest
4e0d5b72e0cf07fb3ad6ad81366067496ea864ab3830c83e42e8ab7dec601657
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4e0d5b72e0cf nginx:latest "/docker-entrypoint.…" 14 seconds ago Up 13 seconds 0.0.0.0:8080->80/tcp, :::8080->80/tcp volume1
ac87c68a7c79 project1:1.5 "/docker-entrypoint.…" 2 hours ago Up 2 hours 80/tcp, 0.0.0.0:8855->8877/tcp, :::8855->8877/tcp stupefied_kalam
# docker exec -it volume1 bash
root@4e0d5b72e0cf:/# cd /usr/share/nginx/html/
root@4e0d5b72e0cf:/usr/share/nginx/html# echo "volume test example" > index.html
root@4e0d5b72e0cf:/usr/share/nginx/html# cat index.html
volume test example
# curl localhost:8080
volume test example
```
```
# docker run -d -p 8181:80 --name volume2 -v my-volume:/usr/share/nginx/html nginx:latest
b5aa20daea070d5ba7d323ac3d32112944d70fe699b581fac5fb9a4832a930ae
# docker exec -it volume2 bash
root@b5aa20daea07:/# cd /usr/share/nginx/html/
root@b5aa20daea07:/usr/share/nginx/html# cat index.html
volume test example
```
### 運行於 Docker 環境中的應用程式 (DNS SERVER ADGUARDHOME)
#### 第一部分 : 拉取鏡像 & 運行
* 拉取鏡像
```
# docker pull adguard/adguardhome
Using default tag: latest
latest: Pulling from adguard/adguardhome
213ec9aee27d: Pull complete
31f9e8542749: Pull complete
35797e6c4bae: Pull complete
3c40b1afc955: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:d6c7532726d132d44cc006a9b25f810225554264a905870fc9dbeb71e7405471
Status: Downloaded newer image for adguard/adguardhome:latest
docker.io/adguard/adguardhome:latest
```
* 運行鏡像 (本機 67/68 port 會被占用,所以改成 167/168 port)
```
# docker run --name adguardhome --restart unless-stopped -v /my/own/workdir:/opt/adguardhome/work -v /my/own/confdir:/opt/adguardhome/conf -p 53:53/tcp -p 53:53/udp -p 167:67/udp -p 168:68/udp -p 80:80/tcp -p 443:443/tcp -p 443:443/udp -p 3000:3000/tcp -p 853:853/tcp -p 784:784/udp -p 853:853/udp -p 8853:8853/udp -p 5443:5443/tcp -p 5443:5443/udp -d adguard/adguardhome
a5ff73392e28a0737ea940aefeaa774bbdb9c14d5407ac3927a8d8708c0ed07a
//--restart unless-stopped : 如果被停止會重新啟動
```
* 占用端口清除 (因為DNS SERVER需要再53端口開啟,但卻被佔用了,這時需要查詢是誰占用53端口,並把它刪除)
```
# netstat -tunlp | grep 53
# kill -9 XXX
```
#### 第二部分 : 連線






# DOCKERFILE 撰寫方式
## 建立 Dockerfile
| 指令 | 內容 |
| ---- | ---- |
| FROM | 使用到的 Docker Image 名稱 |
| MAINTAINER | 說明撰寫和維護的人是誰,也可以給 E-mail 的資訊 |
| WORKDIR | 指定 Docker 執行起來時候的預設目錄位置 |
| EXPOSE | 指定所有發布的 port |
| RUN | 放 Linux 指令,用來執行安裝和設定這個 Image 需要的東西 |
| ADD | 把 Local 的檔案複製到 Image 裡,如果是 tar.gz 檔複製進去 Image 時會順便自動解壓縮 |
| COPY | 複製本機的資料放到容器內 |
| ENV | 用來設定環境變數 |
| CMD | 指定 Instance 啟動後所要執行的指令 |
### Example.
```
FROM 基底映像檔
USER 指定執行容器的使用者名稱或者ID
WORKDIR 工作目錄
LABEL 指定維護者資訊
ENV 建立環境變數可供後續使用
ARG --build-arg 帶入變數,讓build可以結合外部指定建構時所需的參數
COPY 複製本機上的資料至容器
ADD 複製指定的url或是tar檔(自動解壓縮)至指定的目錄
VOLUME 建立一個可以從本幾對外掛載的目錄
EXPOSE 宣告此容器Run在哪個 Port
RUN Build layer 中執行的指令,一個Dockerfile中可以有多個RUN
CMD 當容器啟動之後默認會執行的命令,可以有多個CMD,不過只有最後一個會生效
ENTRYPOINT 指定容器啟動程序及參數
```
### 簡單 Dockerfile
```
# vim Dockerfile
FROM nginx
WORKDIR /app
EXPOSE 8877
COPY index.html /usr/share/nginx/html
COPY default.conf /etc/nginx/conf.d/default.conf
# vim nginx.conf
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
# vim index.html
<p>hello world for dockerfile test</p>
# docker build -t project1:1.5 .
Sending build context to Docker daemon 4.096kB
Step 1/4 : FROM nginx
---> 5d58c024174d
Step 2/4 : EXPOSE 8877
---> Running in f61eaeaff094
Removing intermediate container f61eaeaff094
---> 04101da4691a
Step 3/4 : COPY index.html /usr/share/nginx/html
---> 51a92f98877a
Step 4/4 : COPY default.conf /etc/nginx/conf.d/default.conf
---> 35d18220cde9
Successfully built 35d18220cde9
Successfully tagged project1:1.5
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
project1 1.5 35d18220cde9 2 minutes ago 142MB
# docker run -d --rm -p 8855:8877 project1:1.5
cd55ab764022f8fb108f2860af6a851a3be7c2b395c00d3e9b73fb3293556d49
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cd55ab764022 project1:1.5 "/docker-entrypoint.…" 4 seconds ago Up 2 seconds 80/tcp, 0.0.0.0:8855->8877/tcp, :::8855->8877/tcp charming_dubinsky
a3cbe5317d5a project1:1.2 "/docker-entrypoint.…" 26 minutes ago Up 26 minutes 0.0.0.0:8888->80/tcp, :::8888->80/tcp thirsty_hoover
fbef1936ce47 project1:1.0 "/docker-entrypoint.…" 35 minutes ago Up 35 minutes 0.0.0.0:8866->80/tcp, :::8866->80/tcp web
# curl localhost:8855
hi
# docker exec -it ac8 bash
root@ac87c68a7c79:/app# pwd
/app
```
### 運行於 Docker 環境中的應用程式 (Python)
```
# tree
.
├── app.py
├── Dockerfile
└── requirements.txt
0 directories, 3 files
```
* Dockerfile
```
# vim Dockerfile
FROM python:3.8-slim-buster
WORKDIR /app
COPY . .
RUN pip3 install Flask
RUN pip3 install -r requirements.txt
CMD ["python3","app.py"]
```
* app.py
```
# vim app.py
import os
from flask import Flask
app = Flask(__name__)
HOST = os.getenv('HOST', '0.0.0.0')
PORT = os.getenv('PORT', 5000)
HOST_NAME = os.getenv('HOST_NAME', 'none')
@app.route('/')
def hello_world():
return 'Hello,Docker!This application is running on host: {0}'.format(HOST_NAME)
if __name__ == '__main__':
print('Server is running: {0}:{1} on host name {2}'.format(HOST, PORT, HOST_NAME))
app.run(host=HOST,port=PORT)
```
* 創建映像
```
# docker build -t app .
Sending build context to Docker daemon 3.584kB
Step 1/6 : FROM python:3.8-slim-buster
---> d55c26ea3903
Step 2/6 : WORKDIR /app
---> Running in e94cebcba9cd
Removing intermediate container e94cebcba9cd
---> 4bda9f7b8c43
Step 3/6 : COPY . .
---> 454072492443
Step 4/6 : RUN pip3 install Flask
---> Running in 6ea2fcd9701b
Installing collected packages: zipp, MarkupSafe, itsdangerous, click, Werkzeug, Jinja2, importlib-metadata, Flask
Successfully installed Flask-2.2.2 Jinja2-3.1.2 MarkupSafe-2.1.1 Werkzeug-2.2.2 click-8.1.3 importlib-metadata-5.0.0 itsdangerous-2.1.2 zipp-3.10.0
Removing intermediate container 6ea2fcd9701b
---> 6144b6fd9fa3
Step 5/6 : RUN pip3 install -r requirements.txt
---> Running in 744d5dcb5639
Removing intermediate container 744d5dcb5639
---> dec226758e95
Step 6/6 : CMD ["python3","app.py"]
---> Running in 9c98ff0a17ea
Removing intermediate container 9c98ff0a17ea
---> a593889953fc
Successfully built a593889953fc
Successfully tagged app:latest
```
* 執行容器
```
# docker run -p 8000:5000 app
Server is running: 0.0.0.0:5000 on host name none
* Serving Flask app 'app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://172.17.0.5:5000
Press CTRL+C to quit
172.17.0.1 - - [25/Oct/2022 07:53:35] "GET / HTTP/1.1" 200 -
# curl localhost:8000
Hello,Docker!This application is running on host: none
```
### 運行於 Docker 環境中的應用程式 (Python Flask + Redis 應用)
* Dockerfile
```
# vim Dockerfile
FROM python:3.9.5-slim
RUN pip install flask redis && \
groupadd -r flask && useradd -r -g flask flask && \
mkdir /src && \
chown -R flask:flask /src
USER flask
COPY app.py /src/app.py
WORKDIR /src
ENV FLASK_APP=app.py REDIS_HOST=redis
EXPOSE 5000
CMD ["flask", "run", "-h", "0.0.0.0"]
```
* app.py
```
# vim app.py
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 f"Hello Container World! I have been seen {redis.get('hits').decode('utf-8')} times and my hostname is {socket.gethostname()}.\n"
```
* 下載 Redis 映像檔
```
# docker image pull redis
Using default tag: latest
latest: Pulling from library/redis
e9995326b091: Pull complete
f2cd78d6f24c: Pull complete
8f3614d34c89: Pull complete
697fd51ec515: Pull complete
a554cf50a327: Pull complete
66f93c02e79c: Pull complete
Digest: sha256:aeed51f49a6331df0cb2c1039ae3d1d70d882be3f48bde75cd240452a2348e88
Status: Downloaded newer image for redis:latest
docker.io/library/redis:latest
```
* 創建 Flask-demo 映像
```
# docker image build -t flask-demo .
Sending build context to Docker daemon 3.072kB
Step 1/8 : FROM python:3.9.5-slim
---> c71955050276
Step 2/8 : RUN pip install flask redis && groupadd -r flask && useradd -r -g flask flask && mkdir /src && chown -R flask:flask /src
---> Running in c477e9c5acae
Collecting flask
Downloading Flask-2.2.2-py3-none-any.whl (101 kB)
Collecting redis
Downloading redis-4.3.4-py3-none-any.whl (246 kB)
Collecting Werkzeug>=2.2.2
Downloading Werkzeug-2.2.2-py3-none-any.whl (232 kB)
Collecting itsdangerous>=2.0
Downloading itsdangerous-2.1.2-py3-none-any.whl (15 kB)
Collecting Jinja2>=3.0
Downloading Jinja2-3.1.2-py3-none-any.whl (133 kB)
Collecting importlib-metadata>=3.6.0
Downloading importlib_metadata-5.0.0-py3-none-any.whl (21 kB)
Collecting click>=8.0
Downloading click-8.1.3-py3-none-any.whl (96 kB)
Collecting zipp>=0.5
Downloading zipp-3.10.0-py3-none-any.whl (6.2 kB)
Collecting MarkupSafe>=2.0
Downloading MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (25 kB)
Collecting deprecated>=1.2.3
Downloading Deprecated-1.2.13-py2.py3-none-any.whl (9.6 kB)
Collecting packaging>=20.4
Downloading packaging-21.3-py3-none-any.whl (40 kB)
Collecting async-timeout>=4.0.2
Downloading async_timeout-4.0.2-py3-none-any.whl (5.8 kB)
Collecting wrapt<2,>=1.10
Downloading wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (77 kB)
Collecting pyparsing!=3.0.5,>=2.0.2
Downloading pyparsing-3.0.9-py3-none-any.whl (98 kB)
Installing collected packages: zipp, wrapt, pyparsing, MarkupSafe, Werkzeug, packaging, Jinja2, itsdangerous, importlib-metadata, deprecated, click, async-timeout, redis, flask
Successfully installed Jinja2-3.1.2 MarkupSafe-2.1.1 Werkzeug-2.2.2 async-timeout-4.0.2 click-8.1.3 deprecated-1.2.13 flask-2.2.2 importlib-metadata-5.0.0 itsdangerous-2.1.2 packaging-21.3 pyparsing-3.0.9 redis-4.3.4 wrapt-1.14.1 zipp-3.10.0
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
WARNING: You are using pip version 21.1.3; however, version 22.3 is available.
You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.
Removing intermediate container c477e9c5acae
---> 746e7b1eeb92
Step 3/8 : USER flask
---> Running in 3195047b50fe
Removing intermediate container 3195047b50fe
---> d3bc26ba3d50
Step 4/8 : COPY app.py /src/app.py
---> fdd063765656
Step 5/8 : WORKDIR /src
---> Running in 2a1b120a3818
Removing intermediate container 2a1b120a3818
---> 3831e90d5fd7
Step 6/8 : ENV FLASK_APP=app.py REDIS_HOST=redis
---> Running in fd5c4f9594dc
Removing intermediate container fd5c4f9594dc
---> f635f65302c3
Step 7/8 : EXPOSE 5000
---> Running in 2110f2c1c15c
Removing intermediate container 2110f2c1c15c
---> aaf83e0f20fd
Step 8/8 : CMD ["flask", "run", "-h", "0.0.0.0"]
---> Running in 9131ba8886e0
Removing intermediate container 9131ba8886e0
---> 82a4b8deae23
Successfully built 82a4b8deae23
Successfully tagged flask-demo:latest
```
* 創建 Docker Bridge
```
# docker network create -d bridge demo-network
aa7804bc99a19a1d203b6beca727d581b6a69e74d437a0a1c1b57ecc200b1d1a
# docker network ls
NETWORK ID NAME DRIVER SCOPE
4bd8e05f4cb8 bridge bridge local
aa7804bc99a1 demo-network bridge local
12b814a11be2 host host local
2aa33d0d503f none null local
```
* 創建 Redis 容器
```
# docker run -d --name redis-server --network demo-network redis
78fa815fecbc6440b55eaaef6de58683eeecfb9d40259144685882ff50196756
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
78fa815fecbc redis "docker-entrypoint.s…" 4 seconds ago Up 2 seconds 6379/tcp redis-server
```
* 創建 Flask 容器
```
# docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo
137444e0d1f8eb10398b94c70e1b0b1e53800873eda754c779a729a520fce0d7
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
137444e0d1f8 flask-demo "flask run -h 0.0.0.0" 15 seconds ago Up 14 seconds 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp flask-demo
78fa815fecbc redis "docker-entrypoint.s…" 25 seconds ago Up 23 seconds 6379/tcp redis-server
```
* 開啟瀏覽器 `http://<IP>:5000/` 查看頁面


### 創建一個圖形化界面的 Docker
#### 第一部分 : Dockerfile
* Dockerfile
```
FROM ubuntu:14.04
ENV DEBIAN_FRONTEND noninteractive
ENV HOME /root
RUN sed -i 's/# \(.*multiverse$\)/\1/g' /etc/apt/sources.list
RUN \
apt-get update && \
apt-get install -y build-essential && \
apt-get install -y software-properties-common && \
apt-get install -y byobu curl git htop man unzip vim wget && \
apt-get install -y xorg lxde-core lxterminal tightvncserver && \
rm -rf /var/lib/apt/lists/*
EXPOSE 5901
WORKDIR /root
CMD ["bash"]
```
#### 第二部分 : 創建鏡像 & 執行
```
# docker build -t hello-vnc:1.0 .
# docker run -it --rm -p 5901:5901 -e USER=root hello-vnc:1.0 bash -c "vncserver :1 -geometry 1280x800 -depth 24 && tail -F /root/.vnc/*.log"
You will require a password to access your desktops.
Password:
Verify:
Would you like to enter a view-only password (y/n)? n
```
#### 第三部分 : 使用 vnc viewer 連線
* <IP>:5901

## 結合機器學習
#### 第一部分 : 安裝
```
# wget https://bootstrap.pypa.io/pip/2.7/get-pip.py
# python get-pip.py
# pip install sklearn
```
#### 第二部分 : `train_model.py` & `server.py` & `client.py`
* train_model.py
```
# coding: utf-8
import pickle
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn import tree
# simple demo for traing and saving model
iris=datasets.load_iris()
x=iris.data
y=iris.target
#labels for iris dataset
labels ={
0: "setosa",
1: "versicolor",
2: "virginica"
}
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=.25)
classifier=tree.DecisionTreeClassifier()
classifier.fit(x_train,y_train)
predictions=classifier.predict(x_test)
#export the model
model_name = 'model.pkl'
print("finished training and dump the model as {0}".format(model_name))
pickle.dump(classifier, open(model_name,'wb'))
```
```
# python train_model.py
finished training and dump the model as model.pkl
# ls
get-pip.py model.pkl train_model.py
```
* server.py
```
# coding: utf-8
import pickle
from flask import Flask, request, jsonify
app = Flask(__name__)
# Load the model
model = pickle.load(open('model.pkl', 'rb'))
labels = {
0: "versicolor",
1: "setosa",
2: "virginica"
}
@app.route('/api', methods=['POST'])
def predict():
# Get the data from the POST request.
data = request.get_json(force = True)
predict = model.predict(data['feature'])
return jsonify(predict[0].tolist())
if __name__ == '__main__':
app.run(debug = True, host = '0.0.0.0')
```
```
# python server.py
* Serving Flask app "server" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 152-542-791
192.168.163.138 - - [27/Oct/2022 23:36:42] "POST /api HTTP/1.1" 200 -
192.168.163.138 - - [27/Oct/2022 23:36:45] "POST /api HTTP/1.1" 200 -
192.168.163.138 - - [27/Oct/2022 23:36:46] "POST /api HTTP/1.1" 200 -
```
* client.py
```
# coding: utf-8
import requests
# Change the value of experience that you want to test
url = 'http://192.168.163.138:5000/api'
feature = [[5.8, 4.0, 1.2, 0.2]]
labels ={
0: "setosa",
1: "versicolor",
2: "virginica"
}
r = requests.post(url,json={'feature': feature})
print(labels[r.json()])
```
```
# python client.py
setosa
```
#### 第三部分 : Dockerfile
* Dockerfile
```
# train and run the model with RESTful api
FROM nitincypher/docker-ubuntu-python-pip
COPY ./requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip install -r requirements.txt
COPY train_model.py /app
COPY server.py /app
CMD python /app/train_model.py && python /app/server.py
```
* 創建容器
```
# docker build -t myiris:1.0 .
# docker run -itd --name myiris -p 5000:5000 myiris:1.0
b93a3f4a86b3d69d0911dcf82c85aefad7cf4f5f23ee74e11e0e0144bb4f7330
```
* 測試
```
# python client.py
setosa
```
# DOCKER NETWORK
> Docker network 因應不同需求分為幾種類別,分別是 : Bridge networks、Host networking、Overlay networks、Macvlan networks
#### Bridge networks
* 若無更改 network driver,docker network 預設為 Bridge networks,Bridge networks 通常運用做需要獨立通信的 Container 當中
#### Host networking
* Host networking 會使 Container 的隔離性質消失,在該 Container 當中可以直接使用例如 localhost 來找尋到主機上的 port 或其他資源
#### Overlay networks
* Overlay networks 能使不同 docker daemons 間互相通信,使不同群集的服務能夠交流,亦也能使不同的獨立 Containers 間互相通信
#### Maclvan networks
* Maclvan 允許使用者能將實體網卡設定多個 mac address,並將這些 address 分配給Container 使用,使其在 network 上顯示為 physical address 而非 virtual address。maclvan 希望能讓某些只能連到物理設備的應用程序能夠正常運作
| 參數 | 用途 |
|------|------|
|connect|將容器連接到網路|
|vreate|創建網路|
|disconnect|斷開容器與網路的連接|
|inspect|顯示一個或多個網路的詳細信息|
|ls|列出網路|
|prune|刪除所有未使用的網路|
|rm|移除一個或多個網路|
```
# docker network ls
NETWORK ID NAME DRIVER SCOPE
4bd8e05f4cb8 bridge bridge local
12b814a11be2 host host local
2aa33d0d503f none null local
```
```
# docker network create --driver bridge mybr
bf3f3f2dd57d2fe8ee4d5188beb79b66005dba2909a5f68d60450802bda3c20e
# docker network ls
NETWORK ID NAME DRIVER SCOPE
4bd8e05f4cb8 bridge bridge local
12b814a11be2 host host local
bf3f3f2dd57d mybr bridge local
2aa33d0d503f none null local
# docker network inspect mybr
[
{
"Name": "mybr",
"Id": "bf3f3f2dd57d2fe8ee4d5188beb79b66005dba2909a5f68d60450802bda3c20e",
"Created": "2022-10-25T23:41:25.713906621-07:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
```
# DOCKER-COMPOSE
* 同時建立多容器應用程式
### YAML 檔格式指令
#### `version`
* 指定 yml 使用哪個版本的 compose
#### `build`
* 指定 Dockerfile 所在文件夾的路徑
#### `command`
* 覆蓋容器啟動後默認執行的命令
#### `container_name`
* 指定容器名稱 (默認將會使用 `項目名稱_服務名稱_序號` 這樣的格式)
* `container_name: docker-web-container`
#### `devices`
* 指定設備映射關係
* ```
devices:
- "/dev/ttyUSB1:/dev/ttyUSB0"
```
#### `depends_on`
* 解決容器的依賴、啟動先後的問題,優先權
* ```
version: '3'
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
```
#### `dns`
* 自定義 DNS 服務器 (可以是一個值,也可以是一個列表)
* ```
dns: 8.8.8.8
dns:
- 8.8.8.8
- 114.114.114.114
```
#### `dns_search`
* 配置 DNS 搜索域 (可以是一個值,也可以是一個列表)
* ```
dns_search: example.com
dns_search:
- domain1.example.com
- domain2.example.com
```
#### `env_file`
* 從文件中獲取環境變量,可以為單獨的文件路徑或列表
* ```
env_file: .env
env_file:
- ./common.env
- ./apps/web.env
- /opt/secrets.env
```
#### `environment`
* 設置環境變量。你可以使用數組或字典兩種格式 (只給定名稱的變量會自動獲取運行 Compose 主機上對應變量的值,可以用來防止洩露不必要的數據)
* ```
environment:
RACK_ENV: development
SESSION_SECRET:
environment:
- RACK_ENV=development
- SESSION_SECRET
```
#### `expose`
* 暴露端口,但不映射到宿主機,只被連接的服務訪問
* ```
expose:
- "3000"
- "8000"
```
#### `image`
* 指定為鏡像名稱或鏡像 ID。如果鏡像在本地不存在,Compose 將會嘗試拉取這個鏡像。
* `image: ubuntu`
#### `labels`
* 為容器添加 Docker 元數據(metadata)信息 (可以為容器添加輔助說明信息)
* ```
labels:
com.startupteam.description: "webapp for a startup team"
com.startupteam.department: "devops department"
com.startupteam.release: "rc3 for v1.0"
```
#### `network_mode`
* 設置網絡模式 (使用和 docker run 的 --network 參數一樣的值)
* ```
network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"
```
#### `networks`
* 配置容器連接的網路
* ```
version: "3"
services:
some-service:
networks:
- some-network
- other-network
networks:
some-network:
other-network:
```
#### `ports`
* 暴露端口信息 (使用宿主端口:容器端口 (HOST:CONTAINER) 格式,或者僅僅指定容器的端口都可以)
* ```
ports:
- "3000"
- "8000:8000"
- "49100:22"
- "127.0.0.1:8001:8001"
```
#### `secrets`
* 儲存敏感數據 (密碼)
* ```
version: "3.1"
services:
mysql:
image: mysql
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
secrets:
- db_root_password
- my_other_secret
secrets:
my_secret:
file: ./my_secret.txt
my_other_secret:
external: true
```
#### `volumes`
* 數據卷所掛載路徑設置
* 可以設置為宿主機路徑(HOST:CONTAINER)或者數據卷名稱(VOLUME:CONTAINER),並且可以設置訪問模式(HOST:CONTAINER:ro)。
* ```
version: "3"
services:
my_src:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
```
### Example.
```
version: '3'
services:
app1:
image: imageName
build:
context: .
dockerfile: dockerfilePath/Dockerfile
app2:
image: imageName
```
## 第一部分 : 安裝
```
# curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 11.2M 100 11.2M 0 0 5748k 0 0:00:01 0:00:01 --:--:-- 23.7M
# chmod +x /usr/local/bin/docker-compose
# docker-compose --version
docker-compose version 1.23.2, build 1110ad01
```
## 第二部分 : 撰寫 docker-compose.yml
* docker-compose.yml (注意:縮排很重要)
```
version: '2.3'
services:
flask-demo:
image: flask-demo:latest
container_name: flask-demo
environment:
REDIS_HOST: redis-server
depends_on:
- redis-server
ports:
- "5000:5000"
networks:
- demo-network
redis-server:
image: redis:latest
container_name: redis-server
networks:
- demo-network
networks:
demo-network:
```
## 第三部分 : 啟動
```
# docker-compose up -d
Creating redis-server ... done
Creating flask-demo ... done
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
262859afcc65 flask-demo:latest "flask run -h 0.0.0.0" 6 seconds ago Up 3 seconds 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp flask-demo
bd97f8c42c69 redis:latest "docker-entrypoint.s…" 8 seconds ago Up 5 seconds 6379/tcp redis-server
```
## 第四部分 : 關閉
```
# docker-compose down
Stopping flask-demo ... done
Stopping redis-server ... done
Removing flask-demo ... done
Removing redis-server ... done
Removing network test_flask_demo-network
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
```
## Docker-Compose 運行於 Docker 環境中的應用程式 (APACHE + PHP + MYSQL)
### 第一部分 : 安裝 mysql + php-apache
```
# docker pull mysql
# docker pull radys/php-apache:7.4
```
### 第二部分 : 建立 bridge 網路
```
# docker network create --driver bridge mybridge
5cf62e8432b6d1d62a7e36d4d8264dc78f893eb6cecb92a387960a538dffa791
# docker network ls
NETWORK ID NAME DRIVER SCOPE
5d6223670fd7 bridge bridge local
4800c4d375c5 demo-network bridge local
12b814a11be2 host host local
2d5fd2991d14 mybr bridge local
5cf62e8432b6 mybridge bridge local
2aa33d0d503f none null local
```
### 第三部分 : 產生 MYSQL 容器
```
# docker run -d --network mybridge --name hello-mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3306 mysql
3a41d63af05e9b26ea0383a8fa66dcae61b78b6913728723a4045cfc7770ad45
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3a41d63af05e mysql "docker-entrypoint.s…" 5 seconds ago Up 3 seconds 33060/tcp, 0.0.0.0:49153->3306/tcp, :::49153->3306/tcp hello-mysql
```
### 第四部分 : 進入容器建立資料庫
```
# docker exec -it hello-mysql bash
bash-4.4# mysql -u root -p
mysql> create database testdb;
Query OK, 1 row affected (0.19 sec)
mysql> use testdb;
Database changed
mysql> create table addrbook(name varchar(50) not null, phone varchar(10));
Query OK, 0 rows affected (0.15 sec)
mysql> insert into addrbook(name, phone) values ("eva", "0933928937");
Query OK, 1 row affected (0.09 sec)
mysql> insert into addrbook(name, phone) values ("yc", "0933928937");
Query OK, 1 row affected (0.01 sec)
mysql> select * from addrbook;
+------+------------+
| name | phone |
+------+------------+
| eva | 0933928937 |
| yc | 0933928937 |
+------+------------+
2 rows in set (0.01 sec)
```
### 第五部分 : 創建持久化的 PHP 容器
```
# docker run -d -p 80:80 --network mybridge -v /root/myphp:/var/www/html --name hello-php radys/php-apache:7.4
c896a5bb121826d535dcc7bc2f2f467ae358a84520ccfa4f36fdadb698dcfe2d
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c896a5bb1218 radys/php-apache:7.4 "docker-php-entrypoi…" 7 seconds ago Up 4 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp hello-php
3a41d63af05e mysql "docker-entrypoint.s…" 6 minutes ago Up 6 minutes 33060/tcp, 0.0.0.0:49153->3306/tcp, :::49153->3306/tcp hello-mysql
```
* 測試
```
# vim /root/myphp/test.php
<?php
phpinfo();
?>
```
* 測試結果

### 第六部分 : 顯示資料庫資料
```
# vim index.php
<?php
$servername="hello-mysql";
$username="root";
$password="123456";
$dbname="testdb";
$conn = new mysqli($servername, $username, $password, $dbname);
if($conn->connect_error){
die("connection failed:" . $conn->connect_error);
}
$sql="select name, phone from addrbook";
$result = $conn->query($sql);
if($result->num_rows >0){
while($row = $result->fetch_assoc()){
echo "name:" . $row["name"]." phone:".$row["phone"]."<br>";
}
} else {
echo "0 result";
}
```
* 成果

### 第七部分 : 用 docker-compose 做
* docker-compose.yml
```
version: '2.3'
services:
hello-php:
image: radys/php-apache:7.4
container_name: hello-php
depends_on:
- hello-mysql
ports:
- "80:80"
volumes:
- /root/myphp:/var/www/html
networks:
- mybr
hello-mysql:
image: mysql:latest
container_name: hello-mysql
environment:
MYSQL_ROOT_PASSWORD: 123456
volumes:
- /root/mydbdata:/var/lib/mysql
ports:
- "3306"
networks:
- mybr
networks:
mybr:
```
* 指令
```
# docker-compose up -d
Creating hello-mysql ... done
Creating hello-php ... done
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4309faae535e radys/php-apache:7.4 "docker-php-entrypoi…" 57 seconds ago Up 48 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp hello-php
c3cc212dfa2f mysql:latest "docker-entrypoint.s…" 59 seconds ago Up 57 seconds 33060/tcp, 0.0.0.0:49153->3306/tcp, :::49153->3306/tcp hello-mysql
# docker exec -it c3c bash
bash-4.4# mmysql -u root -p
bash: mmysql: command not found
bash-4.4# mysql -u root -p
mysql> create database testdb;
Query OK, 1 row affected (0.10 sec)
mysql> use testdb;
Database changed
mysql> create table addrbook(name varchar(50) not null, phone varchar(10));
Query OK, 0 rows affected (0.12 sec)
mysql> insert into addrbook(name, phone) values ("eva", "0933928937");
Query OK, 1 row affected (0.08 sec)
mysql> insert into addrbook(name, phone) values ("ycc", "0933928937");
Query OK, 1 row affected (0.00 sec)
mysql> select * from addrbook;
+------+------------+
| name | phone |
+------+------------+
| eva | 0933928937 |
| ycc | 0933928937 |
+------+------------+
2 rows in set (0.02 sec)
```
* 本地儲存資料的地方
```
# cd /root/mydbdata/
# ls
auto.cnf binlog.000002 ca-key.pem client-cert.pem #ib_16384_0.dblwr ib_buffer_pool ibtmp1 #innodb_temp mysql.ibd performance_schema public_key.pem server-key.pem testdb undo_002
binlog.000001 binlog.index ca.pem client-key.pem #ib_16384_1.dblwr ibdata1 #innodb_redo mysql mysql.sock private_key.pem server-cert.pem sys undo_001
```