Docker學習筆記(二)
暑期加入了沃天宇老師的實驗室進行暑期的實習。在正式開始工作之前,師兄先讓我了解一下技術棧,需要了解的有docker、k8s、springboot、springcloud。
謹以一系列部落格記錄一下自己學習的筆記。更多内容見Github
2021/7/6
- 上一篇 Docker學習筆記(一):https://www.cnblogs.com/SnowPhoenix/p/14974321.html
容器的檔案系統
容器間的檔案是互相隔離的,即使它們都使用同一個鏡像。
實驗
docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
-
選項表示detached模式,這個前面已經了解了;-d
-
應當是指定使用的鏡像,因為輸入指令之後我們就可以看到docker從中央倉庫pull下來了ubuntu的鏡像;ubuntu
-
我猜測是指定了運作的程式,bash體驗确實優于sh;bash
-
後面指定了一個運作的指令,我猜測-c
是c
的縮寫,而後面的語句的意思應該是向command
寫入一個1~10000的随機數,然後不斷向控制台輸出/data.txt
中新加入的内容;/dev/null
通過
docker ps
拿到剛剛啟動的容器的id後,我們通過以下指令可以拿到
/data.txt
中的随機數:
docker exec <CONTAINER> cat /data.txt
之後,我們再啟動一個鏡像:
docker run -it ubuntu ls /
-
應當是一個差別于-it
的選項,我們看到這條語句運作完成之後直接在console顯示了結果;-d
-
應當是要在這個容器内執行的程式,因為我們可以看到這個語句完成之後列印了一個目錄的清單;ls /
這裡我們也能看到在上一個run指令中,
-c
其實是傳給
bash
的參數,而不是
docker run
的參數。
從這裡的結果可以看出,新建立的鏡像中沒有第一個鏡像中的
/data.txt
,這就證明了這兩個使用相同鏡像的容器中的檔案系統是互相隔離的,它們互相無法通路到彼此的檔案。
實驗後的小探究
實驗結束後,使用
docker ps
指令可以發現,第一個啟動的容器并沒有停止,而第二個容器已經停止了,通過
docker ps -a
可以看到被停止的容器。
仔細觀察兩次啟動容器的差別,可以發現,第一個容器中,特地使用了
tail -f
,這個指令會進入一個有死循環的程式,隻要不主動退出,就不會完成,而第二個容器中使用的是
ls
,當列印完目錄的内容後,就結束了,此時容器就停止了。
為了探究
-it
的作用,先通過
docker run --help
得知,
-i
的作用是保持
stdin
為開啟狀态,即使沒有attached,而
-t
是打開一個虛拟終端,二者通常一起使用。
直接運作
docker run -it ubuntu
docker run -it ubuntu bash
二者似乎會産生相同的效果,我們可以看到如下效果:

我們進入了一個終端,并且打開另一個Powershell終端輸入
docker ps
,可以發現這個容器并沒有停止,而是處于運作的狀态,并且這個bash前面
root@xxxxxx
中的
xxxxx
就是這個容器的ID。
此外,我們還可以發現,就算啟動時,我們沒有指定
bash
這個參數,
docker ps
COMMAND
一列,還是會顯示為
bash
,看來bash很可能是一個預設的行為,猜測可能跟ubuntu鏡像的Dockerfile相關。
我還對
docker exec
指令試驗了
-it
的效果:
docker exec -it <CONTAINER> bash
這裡的
bash
就不能省略了,否則會報錯。我輸入了第一個容器的id,通過
cat /data.txt
仍舊可以拿出之前獲得的那個随機數。而如果輸入了一個已經停止的容器的id,就會報錯,exec指令隻能用于通路一個正在運作的指令,而不能通路一個已經停止的容器。
從上述的探究來看,一個容器如果其指令全部執行完畢,就會停止,此時無法在通過
docker exec
來進入容器進行操作;倘若我們想要通過
docker exec -it
進入容器操作,我們就需要讓容器進入一個不會退出的程式來保活。不過可能有一些指令能夠讓一個已經停止的容器恢複運作。
實驗結束之後,我們需要清理一下這些容器:
docker rm -f $(docker ps -aq)
這個是搜尋來的指令,其中
docker rm -f
我們已經熟悉了,它表示删除一個容器,如果容器正在運作,就會停止并删除,而後面的部分
docker ps -aq
中,
-q
的作用是隻保留id,這樣它的結果就是一串
CONTAINER_ID
,作為
docker rm -f
Volumes
我也不知道該怎麼翻譯這個詞,Edge自帶的翻譯将它翻譯成了“卷”,我就姑且先叫它
資料卷
吧。
它的出現,就是為了解決容器間/容器-主控端間的資料共享問題。通過容器内到主控端的檔案系統的映射,來實作資料的共享/持久化。
通過如下指令建立一個名為
todo-db
的Volume:
docker volume create todo-db
然後再次啟動我們之前建構好的demo的鏡像:
docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
然後再在3001啟動一遍:
docker run -dp 3001:3000 -v todo-db:/etc/todos getting-started
用浏覽器通路3000和3001,并且在3000添加新的todo,在3001重新整理一下就能發現資料同步過來了:
此後,我們停止并删除這兩個容器之後,再在3002啟動一個新的容器,發現這個todo還是存在的:
這裡發生了什麼事呢?
肯定是和
Volume
的使用相關,我們注意到我們在啟動容器的指令中添加了參數
-v todo-db:/etc/todos
,該指令将容器中的
/etc/todos/
檔案夾綁定到我們剛剛建立的名為
todo-db
的Volume中了,而
/etc/todos/
檔案夾下正是我們啟動的這個項目的sqlite資料庫存放
.db
檔案的目錄,參見
docker\gov-sample-app\src\persistence\sqlite.js
第3行:
const location = process.env.SQLITE_DB_LOCATION || '/etc/todos/todo.db';
而通過
Volume
,我們将sqlite的檔案映射到了主控端上,這樣不同容器間就可以共享這個sqlite檔案,并且容器被銷毀後sqlite檔案不會随之被銷毀。
至于具體被儲存在了主控端的哪裡,通過
docker volume inspect <VOLUME NAME>
來檢視。注意Windows和mac中,docker都是運作在虛拟機上的,是以這裡得到的并不是系統路徑,而是虛拟機中的路徑。
這裡我使用阿裡雲的伺服器進行實驗:(删去了一些個人資訊)
snowphoenix@xxx:~$ sudo docker run -it -v test:/tmp/test ubuntu bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
c549ccf8d472: Pull complete
Digest: sha256:aba80b77e27148d99c034a987e7da3a287ed455390352663418c0f2ed40417fe
Status: Downloaded newer image for ubuntu:latest
root@f1fd90b64c48:/# ls
bin dev home lib32 libx32 mnt proc run srv tmp var
boot etc lib lib64 media opt root sbin sys usr
root@f1fd90b64c48:/# cd tmp
root@f1fd90b64c48:/tmp# ls
test
root@f1fd90b64c48:/tmp# cd test/
root@f1fd90b64c48:/tmp/test# ls
root@f1fd90b64c48:/tmp/test# echo "123" >num
root@f1fd90b64c48:/tmp/test# cat num
123
root@f1fd90b64c48:/tmp/test# exit
exit
snowphoenix@xxx:~$ sudo docker volume inspect test
[
{
"CreatedAt": "2021-07-06T18:40:16+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/test/_data",
"Name": "test",
"Options": {},
"Scope": "local"
}
]
snowphoenix@xxx:~$ sudo cat /var/lib/docker/volumes/test/_data/num
123
我先啟動了一個ubuntu鏡像,并将
/tmp/test
目錄映射到名為
test
的volume中,在容器中,我們發現
/tmp/test
這個目錄已經被建立好了,我在這個目錄下建立一個
num
檔案并寫入
123
。退出容器之後,通過
docker volume inspect test
檢視到了Volume的實際路徑,然後打開了該路徑下的
num
檔案,獲得了在容器中寫入的
123
。
綁定挂載(Bind mounts)
不同于Volume,通過這種方式可以指定主控端與容器映射的檔案夾。方法就是在
-v
選項後面,使用主控端的檔案夾路徑代替
Volume
名。
這種方法可以用于開發時的熱更新,即在容器内運作,在容器外進行開發,并且利用開發工具讓容器内運作的程式随時進行更新。
在指令行切換到
gov-sample-app
檔案夾,然後輸入以下指令運作之前的getting-started:
docker run -dp 3000:3000 -w /app -v "$(pwd):/app" node:12-alpine sh -c "yarn install && yarn run dev"
-
指定了工作目錄;-w
-
指定了映射關系,其中用-v
來表示主控端的目前工作目錄,即$(pwd)
檔案夾;gov-sample-app
-
是使用的鏡像名,注意這裡沒有使用我們建構的getting-started鏡像;node:12-alpine
- 最後是鏡像中運作的指令,這裡以熱更新的模式啟動這個項目。值得注意的是
并沒有bash,是以隻能用sh;Apline
當我們在Windows上執行該指令時,會收到一個警告,因為我們将一個Windows下的檔案夾映射到了一個docker容器中,這将導緻性能較差。
此時通路
localhost:3000
并沒有顯示,通過檢視以下指令檢視運作情況:
docker container logs <CONTAINER>
這個指令可以檢視容器中stdout的輸出。我們可以發現這個時候正在執行
yarn install
,而我們之前先建構鏡像再直接運作的是偶,是很快就能通路
localhost:3000
的,我們看Dockerfile,發現是在Dockerfile的
RUN
語句中執行的,那麼就說明
RUN
語句中的部分是在建構鏡像的時候執行的,那我們可以合理猜測
CMD
中的内容應該是在
docker run
的時候才執行。
等了一陣子,終于結束了建構,可以在
localhost:3000
通路到我們的程式了:
同時,我們可以發現
gov-sample-app
檔案夾下出現了
mode_modules
檔案夾,這個是建構
node.js
項目産生的,檔案數目極其之多,得趕緊加一個gitignore。
然後對
src/static/js/app.js
第109行進行修改:
- {submitting ? 'Adding...' : 'Add Item'}
+ {submitting ? 'Adding...' : 'Add'}
儲存,然後重新整理浏覽器:
我們的更改已經生效了。通過bind-mount方式,我們在本地的更改能夠影響到容器内的檔案。
多容器協作
我們使用容器的時候,應當盡量地解耦,多個應用(如資料庫)最好能夠運作在不同的容器中,這樣能夠更加靈活地進行部署和使用。
而當我們将不同應用部署于不同容器後,我們怎樣讓它們進行協作呢?答案是網絡。這一點和現在分布式的微服務架構的思想有些類似。
啟動MySQL
首先,建立一個網絡network,這個指令與建立一個volume很類似:
docker network create todo-app
然後,啟動一個MySQL鏡像的容器:
docker run -d --network todo-app --network-alias mysql -v todo-mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=todos mysql:5.7
-
表示detached模式運作,老朋友了;-d
-
表示這個容器需要連接配接到我們剛建立的--network
網絡;todo-app
-
表示為這個容器所在的ID起了一個“域名”,在同一個network的其它容器可以通過DNS來找到這個容器;--network-alias
-
表示進行檔案系統的volume映射,注意這裡我們使用了一個名為todo-mysql-data的volume,但我們從未建立過它,事實上,docker将為我們自動建立;-v
-
用來添加環境變量;-e
-
使我們使用的鏡像,我們一般會省略冒号後面的标簽,此時預設的标簽為mysql:5.7
,這裡我們指定了這個标簽;latest
exec
我們可以進入容器檢視狀态:
docker exec -it <CONTAINER> mysql -u root -p
其中密碼是之前通過環境變量指定的
secret
,通過
show databases
指令可以看到MySQL中現在有了我們通過環境變量指定的
todos
資料庫。
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| todos |
+--------------------+
5 rows in set (0.01 sec)
啟動netshoot
netshoot是一個docker/k8s常用的debug工具,用于定位和發現問題的工具箱。詳見:https://github.com/nicolaka/netshoot。
docker run -it --network todo-app nicolaka/netshoot
以互動模式啟動netshoot并且将其添加到todo-app中,即和MySQL位于同一網絡中。
這裡我們使用netshoot的dig指令,這是一個DNS工具,我們獲得了如下結果:
06dfbe6b6d3e ~ dig mysql
; <<>> DiG 9.16.16 <<>> mysql
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20457
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;mysql. IN A
;; ANSWER SECTION:
mysql. 600 IN A 172.18.0.2
;; Query time: 0 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Tue Jul 06 14:11:27 UTC 2021
;; MSG SIZE rcvd: 44
可見通過我們在啟動MySQL時指定的
--network-alias
,成功地添加了一個DNS資訊,使得通過
mysql
域名可以尋找到MySQL容器中的資料庫服務。
利用MySQL作為資料庫啟動原來的項目
docker run -dp 3000:3000 `
-w /app -v "$(pwd):/app" `
--network todo-app `
-e MYSQL_HOST=mysql `
-e MYSQL_USER=root `
-e MYSQL_PASSWORD=secret `
-e MYSQL_DB=todos `
node:12-alpine `
sh -c "yarn install && yarn run dev"
我們這裡定義了很多環境變量,通過這些環境變量,我們可以将資料庫從sqlite切換到MySQL。
我們可以通過
docker container logs
來檢視stdout,可以看到其中包含一下輸出:
Waiting for mysql:3306.
Connected!
Connected to mysql db at host mysql
這說明通過我們設定的MySQL的域名
mysql
連接配接到了之前的MySQL的容器。
通路
localhost:3000
,添加一個TODO:
然後通過
docker exec
進入到MySQL的容器中,可以看到我們的這條資訊成功添加到了資料庫:
mysql> select * from todo_items;
+--------------------------------------+--------+-----------+
| id | name | completed |
+--------------------------------------+--------+-----------+
| ffb87e95-b514-4c33-937c-caee9bcbb5a5 | hellow | 0 |
+--------------------------------------+--------+-----------+
1 row in set (0.00 sec)
Docker Compose
Docker Compose
是一個多容器docker項目的管理工具,通過yml檔案來定義每一個容器(服務啟動的參數)。
在
gov-sample-app
檔案夾下建立
docker-compose.yml
,然後寫上如下内容:
version: "3.7"
services:
app:
image: node:12-alpine
command: sh -c "yarn install && yarn run dev"
ports:
- 3000:3000
working_dir: /app
volumes:
- ./:/app
environment:
MYSQL_HOST: mysql
MYSQL_USER: root
MYSQL_PASSWORD: secret
MYSQL_DB: todos
mysql:
image: mysql:5.7
volumes:
- todo-mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: todos
volumes:
todo-mysql-data:
再在
gov-sample-app
檔案夾下輸入:
docker-compose up -d
即可實作啟動一個MySQL服務再使用這個資料庫啟動我們的node.js服務程式。
相比于在指令行手動啟動這兩個容器,這樣的方式無疑更加友善,就如同手動調用gcc和編寫Makefile使用make工具來建構一樣。
比較有意思的是,我們這次并沒有讓兩個容器添加到同一個network中,它們還是實作了通訊,并且我們沒有定義别名的情況下,node.js仍舊通過
mysql
這個名字路由到了MySQL的容器中。
docker network ls
我們可以發現有一個名為
gov-sample-app_default
的network被建立了。那麼我們可以合理猜測,使用DockerCompose可以使得不同的服務自動添加到同一個defualt網絡下,并且使用服務的名字來作為網絡中的别名。
此外,我在實驗的時候,發現
docker compose
也是可以的(中間少了一個連接配接符),并且在PowerShell下顯示效果更好。
最後,輸入
docker compose down
即可關閉所有容器。
值得注意的是,通過DockerCompose建立的容器、Volume、network的名字都帶有工程名作為字首,也就是說即使在
docker-compose.yml
中指定了
volumes
為
todo-mysql-data
,實際上使用的卻是
gov-sample-app_todo-mysql-data
并且
docker compsoe down
預設是停止并删除所有容器、删除network(default被删除了,不知道其它的會不會被删除),但是不删除volume,除非帶上
--volumes
選項。
鏡像建構的最佳實作
這一部分來自官網。
鏡像安全掃描
docker scan <IMAGE>
該指令使用Snyk對鏡像進行掃描,并給出一些建議和提示。
鏡像層與緩存
通過以下指令我們可以看到鏡像的每一次更改:
docker image history getting-started
IMAGE CREATED CREATED BY SIZE COMMENT
d46ea4c442cd 13 seconds ago CMD ["/bin/sh" "-c" "node src/index.js"] 0B buildkit.dockerfile.v0
<missing> 13 seconds ago RUN yarn install --production # buildkit 83.2MB buildkit.dockerfile.v0
<missing> 37 seconds ago COPY . . # buildkit 58.6MB buildkit.dockerfile.v0
<missing> 28 hours ago WORKDIR /app 0B buildkit.dockerfile.v0
<missing> 28 hours ago RUN /bin/sh -c apk add --no-cache python g++… 205MB buildkit.dockerfile.v0
<missing> 28 hours ago RUN /bin/sh -c sed -i 's/dl-cdn.alpinelinux.… 93B buildkit.dockerfile.v0
<missing> 2 months ago /bin/sh -c #(nop) CMD ["node"] 0B
<missing> 2 months ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B
<missing> 2 months ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B
<missing> 2 months ago /bin/sh -c apk add --no-cache --virtual .bui… 7.62MB
<missing> 2 months ago /bin/sh -c #(nop) ENV YARN_VERSION=1.22.5 0B
<missing> 2 months ago /bin/sh -c addgroup -g 1000 node && addu… 75.7MB
<missing> 2 months ago /bin/sh -c #(nop) ENV NODE_VERSION=12.22.1 0B
<missing> 2 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 2 months ago /bin/sh -c #(nop) ADD file:282b9d56236cae296… 5.62MB
可以看到,大部分我們在
Dockerfile
中寫下的指令,都對應着一次修改。而事實上,每一次修改,Docker都會建立一個新的層(Layer),這也能解釋為什麼我們之前在第二次
docker build
的時候,有些步驟沒有實際執行,因為Docker為其建立了層,隻要層的内容沒有更改,就無須重新執行,隻需要從以前的層那裡開始建構即可。
在這裡,官方文檔也給出了Dockerfile中
COPY
的作用:将主控端中的檔案拷貝至鏡像中。
COPY
本身是無法被cache的,但是,如果
COPY
結束之後,鏡像中的内容跟上一次是一樣的,那麼後面的幾步仍舊可以使用之前的鏡像層。
這樣,就有了最大化利用cache的方法:每次将建構的最小單元拷貝至鏡像中,然後進行建構。比如先拷貝項目的依賴的清單,然後恢複項目依賴,然後再拷貝項目本體,然後建構項目,這樣如果項目依賴的清單沒有改動,則無需再次恢複項目依賴,事實上恢複項目依賴往往占據了大量建構鏡像的時間。
gov-sample-app
項目中,我們就可以先拷貝
package.json
和
yarn.lock
,然後進行
yarn install
,然後再拷貝整個項目。改造後如下(這裡也注釋掉了無用的
apk add
):
# syntax=docker/dockerfile:1
FROM node:12-alpine
# RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
# RUN apk add --no-cache python g++ make
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
CMD node src/index.js
此外,因為我們曾經使用bind-mount直接修改了
gov-sample-app
檔案夾,多了
node_modules
,我們這裡要讓docker忽略掉這個檔案夾,我們需要像
.gitignore
那樣,在
gov-sample-app
檔案夾中建立
.dockerignore
檔案并寫入:
node_modules
此時執行
docker build
,然後改動一下
src/static/index.html
(比如加個回車),再執行
docker build
,
[+] Building 2.1s (12/12) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 32B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 34B 0.0s
=> resolve image config for docker.io/docker/dockerfile:1 1.3s
=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:e2a8561e419ab1ba6b2fe6cbdf49fd 0.0s
=> [internal] load metadata for docker.io/library/node:12-alpine 0.0s
=> [1/5] FROM docker.io/library/node:12-alpine 0.0s
=> [internal] load build context 0.1s
=> => transferring context: 3.46kB 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> CACHED [3/5] COPY package.json yarn.lock ./ 0.0s
=> CACHED [4/5] RUN yarn install --production 0.0s
=> [5/5] COPY . . 0.1s
=> exporting to image 0.2s
=> => exporting layers 0.1s
=> => writing image sha256:ecad588cb07eb9a41fdfede453b0ef587b2afc70a40a7344f4f1ab1ad7b71597 0.0s
=> => naming to docker.io/library/getting-started 0.0s
可以看到除了最後一步,全部使用了Cache。
多個stage
我們在建構的時候和我們部署在生産環境中的時候,往往需要不同的依賴,舊的方式會導緻在最後的鏡像中保留了建構工具,但事實上,這些建構工具應該像手腳架一樣,結束建構就抛棄。
這裡就要再一次提到
FROM
語句,其完整格式為:
FROM <IMAGE> AS <STAGE>
每一個
FROM
語句是一個stage的開始,隻有最後一個stage會被保留在最後的鏡像中(可以通過
--target
參數來指定最後保留在鏡像中的stage)。
官方給了兩個示例:
- Maven+Tomcat:
# syntax=docker/dockerfile:1
FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package
FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps
- React:
# syntax=docker/dockerfile:1
FROM node:12 AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
我們看到,在最後的一個stage中,
COPY
語句采用了
--from=build
來從其它stage拷貝檔案,而不是主控端。
另一方面,在React這裡使用了利用cache的優化操作,可見,即使不在最後的鏡像中,應該也可以利用cache。
總結
後面的學習可能還會回頭來使用docker,是以目前階段的學習就先高一段落。
先回顧開頭的問題:什麼是docker?
用“容器”這個詞來形容docker确實十分形象了,在Windows和macOS上運作需要一個Linux的虛拟機,其實從這裡我們就可以合理推測出來了——docker應該是共享核心的。所有的鏡像使用的應該是主控端的Linux核心。虛拟機需要将方方面面與主控端進行隔離,而docker隻着重于“表層環境”(shell)的隔離。docker的出現,就是為了友善部署,在不同裝置轉移的時候攜帶一定的環境“閉包”,來避免重新安裝環境。
總的來說,docker就是一個用來裝目标應用程式和其依賴的容器,我們可以友善地整個容器整個容器地移動、管理。
docker最主要的兩個操作就是建構和部署。
建構上,需要使用Dockerfile,我們可以控制好COPY的精度來最大化利用鏡像的layer機制進行緩存,提高建構的速度。
部署上,我們利用
docker push
docker pull
來移動鏡像,使用DockerCompose來自動化啟動多個容器。
此外,有2個重要工具:
- vloume/bind-mount,用于檔案的共享/持久化,前者将檔案在主控端的情況交由docker托管,而後者需要手動管理;
- network,利用網絡,使得不同的容器間、容器與主控端間進行通信;
這兩個工具是容器和外界進行通信的重要方式。
遺留問題
RUN和CMD的差別
參考官方文檔:https://docs.docker.com/engine/reference/builder/
RUN
是在建構階段運作的指令,而
CMD
是運作階段運作的預設指令。即前者在
docker build
的時候生效,而後者在
docker run
的時候生效。
關于
CMD
,一個Dockerfile隻有最後一個
CMD
會生效,并且
docker run
指令最後的參數可以覆寫掉它(這可能也是我們在
docker run -it ubuntu
的時候後面跟不跟
bash
效果都一樣的原因)。
并且還有一個和
CMD
類似的指令
ENTRYPOINT
,如果一個鏡像指定了
ENTRYPOINT
,那麼
CMD
的内容(無論是Dockerfile中的CMD還是使用者輸入的)将作為參數傳遞給
ENTRYPOINT
ENTRYPOINT
隻能通過
--entrypoint
在啟動時覆寫。
是以
ENTRYPOINT
有兩種用法,一種是将鏡像包裝成一個應用,這樣隻需要傳遞參數,比較友善,一種就是編寫一個腳本,這個腳本完成一些需要在運作階段進行的初始化工作,然後将傳入的參數作為指令來執行。
WORKDIR有什麼作用
參考官方文檔:https://docs.docker.com/engine/reference/builder/#workdir
相當于在容器内執行
cd
,隻不過如果目錄不存在,會自動建立。