---
# System prepended metadata

title: Docker

---

# Docker 學習筆記

> Docker 部署入門完全指南-圖片速學攻略



# C1
## Q: Docker 是什麼

容器化技術

容器化是可以有效分配 OS 資源的技術。讓 App 獨立

使用場合：部署

![](https://hackmd.io/_uploads/S1i2kaxnh.png)

* 容器是在作業系統層做虛擬化
* VM 是在硬體層實作


## 部署小歷史

問題起源：硬體主機上會安裝作業系統，會在作業系統上部署多個 App。但現在有一個 App 掛掉就可能會波及其他 App

傳統方式：買多個硬體主機解決，一個 App 就是一台主機

VM 模式：OS 上會有 Hypervisor，Hypervisor 讓我們建立 VM。每個 VM 裡面建立不同的 OS。就可以在各自的 OS 上面建立 App。但每個應用都要起一個 VM 做一個前置動作。

容器模式：OS 上透過 Docker Engine，透過 Docker Engine 建立容器。容器會透過 Docker Engine 跟 OS 要資源（記憶體, CPU...），容器裡放 Apps。耗費的資源只有用軟體建立的虛擬空間 ie 容器。


## 圖解架構
![](https://hackmd.io/_uploads/B1Ysnag32.png)

1. 因為要部署，所以先挑一個 OS
2. 為了通用，所以 install docker engine，他會提供一個虛擬化環境(docker container)
3. create docker container，虛擬空間會透過 docker engine 跟 OS 要資源
4. 拿到資源後 docker container 裡面部署 image 

![](https://hackmd.io/_uploads/HklDTaghh.png)


> 透過 Docker Container 可以在不同作業環境上共用。docker hub 提供大家雲端空間進行 images 的上傳。

## Docker 功能

1. 簡化部署流程
2. 跨平台部署
3. 建立乾淨測試環境

![](https://hackmd.io/_uploads/rk6JCTx2n.png)

Note: 
* 安裝指令透過 SQL 將測試資料放到資料庫中。

# C2 & C3

## 如何在不同作業系統上使用

![](https://hackmd.io/_uploads/rJlWyRx2h.png)


結論：
1. Hypervisor 上會建立 VM，目的是要建立出 Linux OS
2. Docker Engine 安裝在 Linux OS 上。安裝好後可以使用它內部功能，起 Container 然後容器裡面放上 App

![](https://hackmd.io/_uploads/H1Pp_Rl22.png)

Note：
* Docker Engine 會跟 Linux 底層的程序 Linux Container 溝通，Linux Container 會管理與分配 Docker Container 的資源
* OS Native 是原生的虛擬化技術，但底層仍在某個位置起一台 virtual box


# C4

![](https://hackmd.io/_uploads/Hy3VjAxn3.png)

1. ==DockerFile 是設定檔==，會吃 source code, base image, other input。執行 dockerFile 後會產生 DockerImage
2. ==Docker Image 是程式部署包==，由 DockerFile 執行指令後產生。
3. ==Docker Container 是虛擬空間==，將程式部署包放入虛擬空間運行，就可以執行程式部署包裡面的內容。==(Docker Image need put into Docker Container)==



# C5 Images 常用語法

![](https://hackmd.io/_uploads/SkAusCxnn.png)

```js=
== U1 雲端下載與使用
docker images

docker container run image_name:tag_name

docker pull image_name:tag_name

== U2 本地建立

// by DockerFile 設定檔，產 images
docker build -t 帳號名稱/imageName . // build images，-t 是給 tag，. 表示會看本地當下目錄叫做 Dockerfile 如果有他就會成功建立

// run images
docker container run image_name:tag_name

// images 建立可以給參數
docker build -t angela100151/course-image-build-02 --build-arg my_name_is="angela" .

== U3 本地上傳雲端

docker push angela100151/course-image-build-03

== U4 本地清理

// 如果有被 container reference，要先把 container 砍掉

docker rm container_編號
docker rmi image_name

// 強制清理 images

docker rmi -f image_name

```

unable to remove repository reference "angela100151/course-image-build-02" (must force) - container 58d2e57b016c is using its referenced image dd48cb4ec2ed


### 小結

| 指令 | 目的 | 其他 |
|-------------------|-------------------------------------------------------|-------------------------------------------------------|
| `docker images` | 檢視有哪些映像檔（images）。 | |
| `docker pull image_name:tag_name` | 從遠端倉庫下載映像檔到本地。 | |
| `docker push image_name:tag_name` | 將本地映像檔上傳到遠端倉庫。 | |
| `docker build -t account_name/image_name` | 根據 Dockerfile 建立映像檔，並指定標籤（tag）。 | images 建立可以給參數 |
| `docker build -t angela100151/course-image-build-02 --build-arg my_name_is="angela" .` | 根據 Dockerfile 建立映像檔，同時提供建構參數 `my_name_is` 的值為 "angela"。 | |
| `docker container run image_name:tag_name` | 執行指定的映像檔建立容器（container）。 | |
| `docker rmi -f image_name` | 強制刪除指定的映像檔，同時刪除相關聯的容器。 | |
| `docker rm container_編號` | 刪除指定編號的容器。 | |
| `docker rmi image_name` | 刪除指定的映像檔，如果有容器在使用該映像檔，則需要先刪除相關聯的容器。 | 有被容器參照時，需先移除相關聯的容器 |


# C6 Container 常用語法



```js=

docker container ls

// 起一個新的 container 跑抓下來的 alpine
docker pull alpine
docker images

docker container run --name c003 alpine ls /
docker run --name c004 alpine ls /  // 起一個容器叫做 c001 使用 apline 這個 images 來跑
	
docker run -it --name c005 alpine /bin/sh

// 讓 contaier 長期可以在背景程式跑
docker run -d --name c006 alpine tail -f /dev/null

// 進入背景程序的 container 做一些事情
docker exec -it 0140fc72d655 /bin/sh

== 練習 docker container run 

docker pull nginx:latest

docker run -d -p 8081:80 --name c007 nginx // -p p 是 port 的意思

echo $(docker-machine ip) // http://127.0.0.1:8081/

== 清空 container
container stop container_id

docker container ls -a // 列出所有 container 不管有無啟動

docker container rm container_id // 將 container 全部清光

```

* `docker container run --name c001 alpine` 跑起來這個 images 後，就可以用這個 images 提供的指令

![](https://hackmd.io/_uploads/Bk-jCRb3n.png)
不會在背景程序一直跑，因為 container 背景運作程序機制是他沒有一個程序要一直跑，他會把自己給清掉


`-it` interactive mode

![](https://hackmd.io/_uploads/BkLZSez32.png)

`tail -f` 會追蹤某個 file 的 log
`/dev/null` 是一個特殊的文件，它會丟棄所有寫入其中的數據


![](https://hackmd.io/_uploads/HyRWfGfnn.png)



vm linux port 8081 對應 container port 80 也就是 ngnix web server 所使用的 port


# ☆	☆	☆	C7 

當使用 docker file 建立 docker image 時，背後整體流程會怎麼運作？

![](https://hackmd.io/_uploads/SkFvS7fhn.png)

docker 架構圖應為以下

![](https://hackmd.io/_uploads/SJI9S7G2n.png)

但為了方便說明，簡化成以下說明

![](https://hackmd.io/_uploads/H1SnrQf2h.png)


* Docker Client 是下指令的 docker terminal
* Build Context 是下 `docker build .` 指令時當下的目錄，目錄裡面一定會有 Dockerfile and Other Files 

VM 起的 Linux Os 上面會安裝 Docker Engine

當 Docker Engine 接收到 `Docker build .` 第一步會先將 macOs 上的 build context copy 到 linux os 裡面

> Docker Engine 裡面又會有不同的世界




兩個工作者
1. Docker Client: docker terminal execute docker 指令
2. Docker Engine：docker 執行的指令都會送到 Docker Engine 上

三個空間
1. 硬體主機上的 mac OS 空間
2. 虛擬主機上建立的 Linux OS 空間
3. Docker Engine 建立起的臨時 Container 的空間


## FROM
alpine 輕量 linux 

* cat 印出檔案內容
* 有了 Dockerfile 就可以建造 image -> `docker build -t <映像檔名稱:標籤> <Dockerfile所在目錄路徑>`
* 有了 images 可以起 container 跑他 -> `docker run angela100151/001`，可以使用這個 images 提供的所有指令



```dockerfile=
cat dockerfile

docker build -t angela100151/001 .

docker images

docker run angela100151/001

# 看到作業系統的版本
docker run angela100151/001 cat /etc/os-release 

# /bin/sh 進入到 container 
docker run angela100151/001 /bin/sh
# 進入互動模式
docker run -it angela100151/001 /bin/sh
# container 在背景不斷運行
docker run -d angela100151/001 tail -f /dev/null
# 檢視目前的 docker container 有哪些
docker container ls
# 進入 container
# 因為已經有 container 所以這邊要寫的是 container id  
 docker exec -it 302385395fd6 /bin/sh
```

![](https://hackmd.io/_uploads/ByucT4gp3.png)

docker 沒事做，就會自己不見


| 參數            | 說明                                                         |
|----------------|------------------------------------------------------------|
| `docker run`   | 啟動一個新的容器實例。                                         |
| `-d`           | 在背景運行容器（分離模式）。                                    |
| `angela100151/001` | 要運行的Docker映像（容器的基本映像）。                         |
| `tail -f /dev/null` | 在容器內運行的命令，這裡是讓容器保持運行但不執行實際操作，常用於保持容器處於活動狀態。|

這個指令的作用是以分離模式在背景中運行一個Docker容器，使用`angela100151/001`映像，並運行`tail -f /dev/null`命令來保持容器運行，而不執行實際的操作。這種技巧常用於需要容器保持活動狀態，但不需要執行實際應用程序的情況，例如網絡伺服器等。


上述做的事情都是 Dockerfile 裡，FROM 提供我們基本的語法

```dockerfile=
# 找baseImage的來源
FROM alpine:latest 
```

## ENTRYPOINT

`ENTRYPOINT ["tail", "-f", "/dev/null"]`
用途：啟動 images 時，預設語法要跑什麼
定義：DOCKER RUN 某 IMAGE，這個 IMAGE 會第一個執行的指令
語法：`ENTRYPOINT ["executable", "param1", "param2"]`


```dockerfile=
# 在 image 後面手動 key 了這個語法，讓他可以在背景程式後面一直跑
docker run -d angela100151/001 tail -f /dev/null

# 如果想提供這個 image 給他人用，但又不希望他一定要打上 tail -f /dev/null 這個語法
# 可以透過 ENTRYPOINT 的 Dockerfile 語法給予他預設的啟動指令

docker build -t angela100151/002

# 不需要再加上 tail -f /dev/null
docker run -d angela100151/002
```

## Run

進入到指定的 container 中，起一個 apache server

因為要能夠連到裡面，所以要做 port mapping

docker building 有三空間：本地 OS 空間，VM Linux 空間，Container 空間

8080 指的是 VM Linux 空間，80 指的是 Container 空間裡面的 port 

```dockerfile=

docker run -d -p 8080:80 angela100151/002

docker container ls

# 進入這個 contaier
docker exec -it 71e10a8a5a52 /bin/sh

# 以下指令是為了安裝 apache server 的指令

# apk is alpine 安裝套件指令
apk --update add apache2

# clear cache
rm -rf /var/cache/apk/*

# 啟動 apached server
httpd -D FOREGROUND
# check docker-machine ip 找到 linux vm ip 位置，他會幫我們對照 container 裡面的 80 port

docker inspect <container id> | grep "IPAddress"
echo $(docker-machine ip) 
# http://127.0.0.1:8080/

# 將上述打包的過程在封裝一層，讓使用更方便


```

* Run 用途：使用任意 base image 提供的指令
* 時間點：在 image building 時被執行，而非跑 container 時被執行
* 效果：在 dockerFile building 過程中，使用 base image 所提供的指令


```dockerfile=
FROM alpine:latest 
RUN apk --update add apache2
RUN rm -rf /var/cache/apk/*
ENTRYPOINT [ "httpd", "-D", "FOREGROUND" ]
```

```dockerfile=
docker run -d -p 8082:80 angela100151/004
```

> 有這三個語法基本上可以組出全世界 FROM RUN ENTRYPOINT


## ENV 語法：建立共同變數

想對 apache server 寫一些首頁內容

對每個 run 指令，Docker Daemon 都會起臨時性的 container 並將他包成一包新的 image，再往下

兩個 run 指令之間是沒有相通，run 會起一個臨時性的 container

![](https://hackmd.io/_uploads/rJF27MJRn.png)

![](https://hackmd.io/_uploads/r1EX4fJAh.png)



```dockerfile=

FROM alpine:latest 
RUN apk --update add apache2
RUN rm -rf /var/cache/apk/*
RUN cd /var/www/localhost/htdocs
RUN pwd
ENTRYPOINT [ "httpd", "-D", "FOREGROUND" ]

# 加上 && 就會讓 Run 一次跑兩個指令

FROM alpine:latest 
RUN apk --update add apache2
RUN rm -rf /var/cache/apk/*
RUN cd /var/www/localhost/htdocs && pwd
ENTRYPOINT [ "httpd", "-D", "FOREGROUND" ]

# run 裡面就可以讓他不同指令但在同一行跑，使用 \
FROM alpine:latest 
RUN apk --update add apache2
RUN rm -rf /var/cache/apk/*
RUN cd /var/www/localhost/htdocs \
    && echo "<h3>I am Angela. I am taking this great course. Round 01</h3>" >> index.html
RUN cd /var/www/localhost/htdocs \
    && echo "<h3>I am Angela. I am taking this great course. Round 03</h3>" >> index.html
RUN cd /var/www/localhost/htdocs \
    && echo "<h3>I am Angela. I am taking this great course. Round 02</h3>" >> index.html
ENTRYPOINT [ "httpd", "-D", "FOREGROUND" ]


```

`docker run -d -p 8080:81 angela100151/005`

確認首頁信息已成功被更動
`http://127.0.0.1:8080/`

每個 run 都是自己的世界，都要透過 cd 進到自己的資料夾
現在想將共同的部分統一寫

```dockerfile=
FROM alpine:latest 
ENV myworkdir /var/www/localhost/htdocs
RUN apk --update add apache2
RUN rm -rf /var/cache/apk/*
RUN cd ${myworkdir} \
    && echo "<h3>I am Angela. I am taking this great course. Round 01</h3>" >> index.html
RUN cd ${myworkdir} \
    && echo "<h3>I am Angela. I am taking this great course. Round 03</h3>" >> index.html
RUN cd ${myworkdir} \
    && echo "<h3>I am Angela. I am taking this great course. Round 02</h3>" >> index.html
ENTRYPOINT [ "httpd", "-D", "FOREGROUND" ]
```

* `\` 表示換行符號，在 Dockerfile or shell 可以這麼撰寫
* `>>`是重定向符號，用於將命令的輸出附加到文件中
* `cat` 顯示文件 echo 顯示文本


`docker run -d -p 8080:81 angela100151/005`

## workdir：指定預設目錄的位置

想將重複的 cd 過程模組化

用途：自動將預設路徑 cd 到指定的路徑

```dockerfile=
FROM alpine:latest 
ENV myworkdir /var/www/localhost/htdocs
WORKDIR ${myworkdir}
RUN apk --update add apache2
RUN rm -rf /var/cache/apk/*
RUN echo "<h3>I am Angela. I am taking this great course. Round 01</h3>" >> index.html
RUN echo "<h3>I am Angela. I am taking this great course. Round 03</h3>" >> index.html
RUN echo "<h3>I am Angela. I am taking this great course. Round 02</h3>" >> index.html
ENTRYPOINT [ "httpd", "-D", "FOREGROUND" ]
```

`docker run -d -p 8080:80 angela100151/006`

## ARG

希望將 Angela 變成動態的變數，讓我在建立 images 時去改變它

ARG 與 ENV 差異在他可以在 docker build 的時候動態改變他的變數

```dockerfile

FROM alpine:latest 
ENV myworkdir /var/www/localhost/htdocs
ARG whoami=Angela
WORKDIR ${myworkdir}

RUN apk --update add apache2
RUN rm -rf /var/cache/apk/*
RUN echo "<h3>I am ${whoami}. I am taking this great course. Round 01</h3>" >> index.html
RUN echo "<h3>I am ${whoami}. I am taking this great course. Round 03</h3>" >> index.html
RUN echo "<h3>I am ${whoami}. I am taking this great course. Round 02</h3>" >> index.html
ENTRYPOINT [ "httpd", "-D", "FOREGROUND" ]
```

建立新的 image

```dockerfile
docker build --build-arg whoami=Tony -t angela100151/007
```

## Copy: 將外界檔案資料移到 Container 中

linux vm 空間複製一份到 container 當前目錄底下。container 當前目錄定義在 workdir 這邊


```dockerfile!

FROM alpine:latest 
ENV myworkdir /var/www/localhost/htdocs
ARG whoami=Angela
WORKDIR ${myworkdir}

RUN apk --update add apache2
RUN rm -rf /var/cache/apk/*
RUN echo "<h3>I am ${whoami}. I am taking this great course. Round 01</h3>" >> index.html
RUN echo "<h3>I am ${whoami}. I am taking this great course. Round 03</h3>" >> index.html
RUN echo "<h3>I am ${whoami}. I am taking this great course. Round 02</h3>" >> index.html
COPY ./content.txt ./
RUN ls -l ./
RUN cat ./content.txt >> index.html
ENTRYPOINT [ "httpd", "-D", "FOREGROUND" ]
```

## HW

## 將任意 Container 變成 Docker Image

![](https://hackmd.io/_uploads/ryJtEsxT2.png)

```dockerfile!

docker build -t angela100151/myimage

docker run -d -p 8080:80 angela100151/myimage

# 進入 container 更動首頁頁面
docker exec -it 6c146aff59ff /bin/sh

echo "I am going to turn this container into n new image" >> index.html

cat index.html

exit
# 將運行中的 container 轉換成 image
docker commit 6c146aff59ff angela100151/containertoimage
# 檢視新增的 image
docker images
# 檢視所有 container
docker container ls
# 停掉並砍掉
docker stop 6c146aff59ff
docker rm 6c146aff59ff
# 確定剛剛 container 有成功轉換成 image
docker container run -d -p 8080:80 angela100151/containertoimage
```

![](https://hackmd.io/_uploads/H1GlPieTn.png)

# C8 Docker 網路模式

![](https://hackmd.io/_uploads/SJEGLNNTh.png)

三種空間有各自對應的網路空間


| Space           | Network           |
| --------------- | ----------------- |
| host            | Host Network      |
| VM              | Linux VM Network  |
| Docker Engine   | Bridge Networks   |

Bridge Networks 的四種模式
1. None 模式：容器與外界沒有任何接觸。容器無法外連，外面也連不進去。(封閉網路空間)
2. Bridge 模式：容器啟動時，會放入某個 Bridge Network 中，在 Bridge Nerwork 裡面有所謂的 IP，容器會跟他要 IP 來使用。不同容器要屬於同一個網路空間才能互通。
3. Container 模式：起一個 Container，對應現行的 Container。
4. Host 模式：與 Linux VM 要 IP。所可以跟 VM 虛擬主機上的所有應用互通網路。

A1 B1 B2 都是 IP

Remark:
* Docker 的世界上有多種 Network



## Docker 網路的 None 模式

1. 起一個新的 container 指定使用 none 的 network 模式
2. 確認 container 無法跟外界溝通

```dockerfile=
#列出所有 network 模式
docker network ls 

# 起一個 none 模式的 container
docker run -d --network none --name none-mode alpine tail -f /dev/null

# 檢視 none 模式底下是否有剛剛建立好的 container
docker network inspect none

# 連進去剛建立好的 container 試圖讓他跟外界溝通
 docker exec -it 199e4dac31f4 /bin/sh
 ip addr ls
 ping 8.8.8.8
```

![](https://hackmd.io/_uploads/Bki-O6MC3.png)


## Docker 網路的 Bridge 模式
1. 建立 bridge 的網路空間
2. 檢視 bridge network 的資訊
3. 建立新的 container 放到這個網路空間中
4. 連入這個 container 
5. 確定 bridge mode 可以連到外網
6. 建造第二個 container 放到 bridge 裡面
7. 測 2 個 container 能否互通
8. 建造第三個 container 放到不同的 bridge 中


不同 bridge 之間的 container 是不能互通

```dockerfile=
# check current network space
docker network ls

# create personal network space using bridge mode
docker network create --driver bridge my-bridge

# check 
docker network inspect my-bridge

# build new container put them to my-bridge network
docker run -d --network my-bridge --name bridge-mode-001 alpine tail -f /dev/null
 
# inspect my-bridge
docker network inspect my-bridge
 
# go into this container to see its network 
docker exec -it 06d6334aefc8b415773d964aeea81e34f634cb4560ee08c2cca5445872663924 /bin/sh

ip addr ls

ping 8.8.8.8

# similarly, create second container and put it to the my-bridge network 

docker run -d --network my-bridge --name bridge-mode-002 alpine tail -f /dev/null

docker container ls

docker network inspect my-bridge

ping 8.8.8.8

# 02 & 03 可以成功互通
ping 172.18.0.2 

# create third container put it to the different container space
docker network create --driver bridge thier-bridge
docker network ls

docker run -d --network thier-bridge --name bridge-mode-003 alpine tail -f /dev/null


docker network inspect thier-bridge

docker exec -it d55431026bf65bcb31bb84680fdc9725d2816dae9ebbe6bb5d7a042f7b8c2a66 /bin/sh

# ping 不過去，因為不同 birdge 網路空間不互通
ping 172.18.0.2/16


# 將 03 加入到 02 網路空間
# connect 後面加上 bridge 名稱與要移過去的 container 名稱
docker network connect my-bridge  bridge-mode-003
# 003 現在有兩個 ip
docker network inspect thier-bridge

docker exec -it d55431026bf65bcb31bb84680fdc9725d2816dae9ebbe6bb5d7a042f7b8c2a66 /bin/sh

ping 172.18.0.2/16
# 確定這三個網路空間都互通
```

bridge 內的網路空間
![](https://hackmd.io/_uploads/ryPB3azC3.png)

我們是從 my-bridge 這個網路空間中拿一個 ip 給 001 用
![](https://hackmd.io/_uploads/ry92pTMA2.png)

thier-bridge 內的網路空間
![](https://hackmd.io/_uploads/H1-CZCMA3.png)


```text=
my-bridge: 172.18.0.0/16
bridge-mode-001: 172.18.0.2/16
bridge-mode-002:172.18.0.3/16
bridge-mode-003:172.18.0.4/16

their-bridge:172.19.0.0/16
bridge-mode-003:172.19.0.2/16
```


## Docker 網路的 Container 模式


```dockerfile=
# 確定目前有的 container

# container:container_名稱
# 表示要拷貝那一個 container 的網路設定
docker run -d --network container:bridge-mode-001 --name container-mode-001 alpine tail -f /dev/null

# container 沒有看到新創建的 container-mode-001 因為 container mode 是拷貝現有的網路設定，他不會新增任何東西
docker network inspect my-bridge

ping 8.8.8.8
ping 172.18.0.3
```

1. 起一個新的 container

container mode 是拷貝現有的網路設定，他不會新增任何東西

```text=
my-bridge: 172.18.0.0/16
bridge-mode-001: 172.18.0.2/16
bridge-mode-002:172.18.0.3/16
bridge-mode-003:172.18.0.4/16

their-bridge:172.19.0.0/16
bridge-mode-003:172.19.0.2/16

container-mode-001: 172.18.0.2/16
```


## Docker 網路的 Host 模式

host mode 就是讓 container 的網路世界與 VM 相同

container 在網路中不再屬於 container 那層，而是被當成與 linux VM Host 相同。

```dockerfile=
# 沒有 -p 是因為 host 模式將 container 層級拉到跟 vm 一樣的地方
docker run -d --network host --name my-apache angela100151/my-apache

docker inspect host

# container 中對外開放的 port 是什麼
netstat -tulpn
# tcp        0      0 :::80                   :::*                    LISTEN      1/httpd
```



# Docker Volume

![](https://hackmd.io/_uploads/HymRkua6n.png)

Container Disk 空間出現的資料是暫時的，Container 消失資料就會不見

VM 空間會被保存下來。我們可以建立 container 去使用 VM 空間的東西，儘管後來將 container 砍掉，存在 VM 空間的資料還是不會消失。

## 實作

1. 建立 images
2. 跑建立好的 images
3. 進入 container 中，看到檔案內容
4. 關掉與清掉 container
5. 重新起 container

Mountpoint 是 Linux VM 的路徑

將 VM Disk 空間與 Container Disk 空間 mapping

如果沒有指定 volume，Docker Engine 會自動幫我們建立一個 value

```dockerfile=
# create image 
docker build -t angela100151/apache-001 .

# run container 
docker run -d -p 8080:80 angela100151/apache-001

# enter this container
docker exec -it 2f007e6edd6c /bin/sh

echo 'I love u' >> index.html

# clear this container 
docker container stop 2f007e6edd6c
docker container rm 2f007e6edd6c

# 重新啟動 container 
# 剛剛的 I love u 已經不見了
docker run -d -p 8080:80 angela100151/apache-001

# use container volume to protect history data

# check current volume
docker volume ls

# create new volume
docker volume create mainpage-vol

docker volume inspect mainpage-vol

# 將 vm volume 路徑 "Mountpoint": "/var/lib/docker/volumes/mainpage-vol/_data", map 到 container 裡面的路徑
# vm 空間:container 空間 image 名稱
# 這邊會去 map 兩個空間，讓他們的改動都可以互相連動
docker run -d -p 8081:80 -v mainpage-vol:/var/www/localhost/htdocs/ angela100151/apache-001

# 進入這個 container 對首頁進行一些修改
docker exec -it 0e8cc0f21b70 /bin/sh
echo 'I love u so much' >> index.html

# clear container 
docker container stop 0e8cc0f21b70
docker container rm 0e8cc0f21b70

# 重啟這個 container 發現 volume 是可以真的保存這個 container 的資訊

```
![](https://hackmd.io/_uploads/rk7SuJm02.png)

沒有 -v 後面接上指令的 volume，docker engine 會自動幫我們建立一個亂數的 volume

![](https://hackmd.io/_uploads/rkeuqkQ0n.png)



# 大管家 Docker Compose 

透過 Docker Compose 可以將 service network volume 統整起來一起處理

![](https://hackmd.io/_uploads/SJMWN_aTh.png)

1. 將 service network volume 統整
2. 透過 docker compose 可以一次起多個 container

## Docker Compose 實作 Services(Container)

```yaml=
# 建造 docker-compose 設定檔

# version 語法要用 3.7 版本
version: "3.7"
# services 部分就是 container 部分
services:
  myweb: # container name
    build: 
      context: . # dockerFile 所在的目錄位置
      args: # dockerFile 中的 args
        name: '蠟筆小新'
    image: angela100151/myweb:latest
    ports:
      - "8080:80"
```



```dockerfile=
# no cache 會讓他每次 build 都跑一次，避免沒有更新到
docker-compose build --no-cache

# 透過 compose 起 container
# -d 是指放在背景執行
docker-compose up -d

docker container ls

# 停止並移除由 docker-compose up 建立的容器、網路
docker compose down
```


```yaml=
# docker compose 可以起多個 container

# version 語法要用 3.7 版本
version: "3.7"
# services 部分就是 container 部分
services:
  myweb: # container name
    build: 
      context: . # dockerFile 所在的目錄位置
      args: # dockerFile 中的 args
        name: '蠟筆小新'
    image: angela100151/myweb:latest
    ports:
      - "8080:80"
  myweb2: # container name
    build: 
      context: . # dockerFile 所在的目錄位置
      args: # dockerFile 中的 args
        name: 'Angela'
    image: angela100151/myweb2:latest
    ports:
      - "8081:80"
  myweb3: # container name
    build: 
      context: . # dockerFile 所在的目錄位置
      args: # dockerFile 中的 args
        name: 'Tom'
    image: angela100151/myweb3:latest
    ports:
      - "8082:80"
```

```dockerfile=
# build images
docker-compose build --no-cache

# make sure images
docker images 

# run docker container 
docker-compose up -d
# close container
docker-compose down
```

```yaml=
# 從現有 image 起 container
# version 語法要用 3.7 版本
version: "3.7"
# services 部分就是 container 部分
services:
  myweb: # container name
    build: 
      context: . # dockerFile 所在的目錄位置
      args: # dockerFile 中的 args
        name: 'Bob'
    image: angela100151/myweb:latest
    ports:
      - "8080:80"
  myweb2: # container name
    build: 
      context: . # dockerFile 所在的目錄位置
      args: # dockerFile 中的 args
        name: 'Angela'
    image: angela100151/myweb2:latest
    ports:
      - "8081:80"
  myweb3: # container name
    build: 
      context: . # dockerFile 所在的目錄位置
      args: # dockerFile 中的 args
        name: 'Tom'
    image: angela100151/myweb3:latest
    ports:
      - "8082:80"
  myweb4: # container name
    image: angela100151/myweb:latest
    ports:
      - "8083:80"
```
	  


## Docker Compose 實作 Networks


using network property

```yaml=
# version 語法要用 3.7 版本
version: "3.7"
# services 部分就是 container 部分
services:
  myweb: # container name
    build: 
      context: . # dockerFile 所在的目錄位置
      args: # dockerFile 中的 args
        name: 'Bob'
    image: angela100151/myweb:latest
    ports:
      - "8080:80"
    networks:
      - mybridge001
  myweb2: # container name
    build: 
      context: . # dockerFile 所在的目錄位置
      args: # dockerFile 中的 args
        name: 'Angela'
    image: angela100151/myweb2:latest
    ports:
      - "8081:80"
    networks:
      - mybridge001
  myweb3: # container name
    build: 
      context: . # dockerFile 所在的目錄位置
      args: # dockerFile 中的 args
        name: 'Tom'
    image: angela100151/myweb3:latest
    ports:
      - "8082:80"
    networks:
      - mybridge001
  myweb4: # container name
    image: angela100151/myweb:latest
    ports:
      - "8083:80"
    networks:
      - mybridge002
networks: # 透過 networks 建立客製化網路空間
  mybridge001:
  mybridge002:
```


```dockerfile=

# 將所有 container 背景執行
docker-compose up -d

# check network 
docker network ls

# build images
docker-compose build --no-cache

# 查看透過 network 建立客製化 bridge 空間
docker network inspect  docker_mybridge001
docker network inspect  docker_mybridge002

```


## Docker Compose 實作 Volumes

同前面步驟，這次新增 myweb5

```yaml=
  myweb5: # container name
    image: angela100151/myweb:latest
    ports:
      - "8084:80"
    networks:
      - mybridge002
    volumes:
      - mainpage-vol002:/var/www/localhost/htdocs/ # mainpage-vol002 座落在 linux vm 空間將他 map 到 container 的這個目錄底下
networks: # 透過 networks 建立客製化網路空間
  mybridge001:
  mybridge002:
volumes:
  mainpage-vol002:
```


## Infrastructure as Code
> 讓整體 IT 環境濃縮在單一設定檔案。透過設定檔一鍵部署就可以起完整個 IT 環境。

![](https://hackmd.io/_uploads/Skk-adTp3.png)

作法

* 容器化方式部署多個 container ：以 docker compose 前後端部署為例，每個 container 進行前端後端資料庫的部署。IT 環境變成三個 container 各自部署各自的應用程式
* 雲端進行部署：以 AWS 為例，撰寫 Colud Formation 設定檔，就可以在 AWS 上部署多個 VM

![](https://hackmd.io/_uploads/S1Hv2GXR3.png)


# C11 應用

![](https://hackmd.io/_uploads/BJ_qC_663.png)

角色
* web：前端
* app：後端
* db：資料庫

File
.env：環境參數檔案

將各個專案的 source code 打包成自己的 image，然後放到容器跑

```dockerfile=
# Build or rebuild services
# 有改動 docker-compose & DB 測試資料語法，就需要再重新跑一次
docker-compose build --no-cache

# Create and start containers
docker-compose up -d
# 將本地 image 通通砍掉
docker rmi $(docker images -a -q)

# 建立一個 CPU 規格的 image
docker-compose push

# 可以一次創建多個 CPU 規格的 image
docker buildx create --use --name mybuilder


```


![](https://hackmd.io/_uploads/HyySYNVRh.png)

將 volume 拿掉，所以每次容器啟動與關掉，DB 資料不會留著。

* mac 的 docker 跨平台部署要考慮 CPU 架構

不同 CPU 架構要考慮其 image

```dockerfile=

# 將 compose yaml 設定檔，上傳到 dockerhub 上
# 這個指令目前無法完整運作，只會建立一個 CPU 規格的 image
docker-compose push

# rm all images
docker rmi $(docker images -a -q)

# docker buildx 會創建多個 CPU 規格的 image
docker buildx create --use --name mybuilder
# assing CPU 規格
# --push dockerFile_位置
docker buildx build --platform linux/arm64,linux/adm64 -t angela100151/mysql-db-01 --push ./
```

* 跨平台部署示範

已經從雲端抓下 images，就不用再 build。這時只要 `docker-compose up -d`

```dockerfile=
# 抓下 dockerhub image
docker pull angela100151/image_name

# 一次抓下 docker-compose 裡面的 images 們
docker-compose pull
```

![](https://hackmd.io/_uploads/Sy9cMDrC2.png)

![](https://hackmd.io/_uploads/ByazmDBAh.png)




# C12 規模化部署

## docker swarm
![](https://hackmd.io/_uploads/rJ6D6GQA2.png)

當這些 docker engine 進入 swarm mode 之後，會變成 Node。Node ＆ Docker engine 是一樣的東西，只是稱呼不同而已。Node 之間有不同角色，最重要的角色為 Manager Node。


* Manager Node：統整 Node 之間的溝通
	* 實際運作說明：首先會給 Manager Node 一個叫 service 的東東，service 由 image & 啟動指令組成。這次要執行哪個 docker image，當這個 image 在 container 啟動要執行哪個指令先。將 image 交給 service 執行後會啟動多個 tasks，每個 tasks 都會使用同個 service。
	* Manager Node 的目的是維持目標狀態，tasks 類似 slot 需要被實際運作。Work Node 會幫助 task 運作。不同 task 有可能分配到相同 Work Node 執行。當 tasks 都成功執行，Manager Node 就認定已經到達目標狀態。
	* Manager Node 不在乎在哪台主機或是哪個 Work Node 去執行，他只在乎 tasks 有無人去執行
* Worker Node：實際執行。每個 Worker Node 會向 Manager Node 註冊，加入 Manager Node 的管理。被指派的 Worker Node 會起一個全新的 Container 去執行 Task，並將執行完的結果向 Manager Node 會報。

## 規模化部署方案比較

![](https://hackmd.io/_uploads/H17tm77Rn.png)

## GKE 實作

名詞解釋
* Cluster：Cluster Manager。統籌與運算資源
* Work Load: 可以客製化 Workload including dockerFile, docker images, work numbers
* Services & Ingress：對外開放 containers
* Node Pool：Work Node。 

定義工作包讓 Node 有事情做
* Edit Container：要用哪個 image，可用既有或用新的
* Dockerfile 在根目錄就不用特別指定位置
* Exposing services：對外開放，做 port mapping

Docker 的世界中 container 是實際去執行的單位。k8s 在 container 上用多蓋了一層叫 pods。一個 pods 可以有多個 containers，要做的事情都是一樣的，將 tasks 領下來做掉。

# C13 

# podman & docker 的優缺比較
![](https://hackmd.io/_uploads/r1YGTyQRh.png)


所有 docker 指令都是透過 docker daemon 接手做下去。docker daeom 需要有 root 權限，這樣才可以在任意 port & root 做操作。

docker 底層是 containerd runtime

podman 一樣會起 podman client 可是中間不需要一個 daemom 長期執行，以開發者權限執行這些指令。

每個 podman 指令會叫起不同的 container

podman 底層使用 OCI

Q：為什麼 podman 會取代 docker？
1. 安全性: 駭客攻擊 root 取得權限就可以操作 docker 
2. single point failre：docker daemomn
3. podman 生態系：podman 類似 module 概念，周邊有很多 plugins 

# k8s
* docker 貨櫃船上的貨櫃：將所有應用程式需要的東西打包成一包
* k8s 掌舵者(統整者)：將貨櫃打理放到合適的位置，將貨櫃運出去(部署出去)

k8s核心概念 + 雲端

學習歷程

![](https://hackmd.io/_uploads/SklggeQA2.png)

![](https://hackmd.io/_uploads/B1oHgx7Ah.png)

![](https://hackmd.io/_uploads/SJpDlgQR2.png)

# k8s & docker 之間的關係

k8s 作為掌舵者，需要兼容不同的容器執行環境。他建立 CRI 要求所有人配合這個介面。

因為 CRI 晚於 docker 所以並沒有兼容 docker。
透過 docker-shime 將 dokcer 兼容於 CRI。

為了要維護 docker-shime 成本太大，所以現在宣布不維護 docker-shime
![](https://hackmd.io/_uploads/SJNseeXA2.png)

不同的容器執行環境底層都符合 OCI 規範，因為底層規範相同，所以製作出的 images 是可以被所有 runtime 執行。

影響
* 開發者：還是可以用 docker image
* 維運者：要去尋找不同的 run time 

![](https://hackmd.io/_uploads/SyhhXONAn.png)
