天天看點

Docker學習筆記(二)

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"
           
  • -d

    選項表示detached模式,這個前面已經了解了;
  • ubuntu

    應當是指定使用的鏡像,因為輸入指令之後我們就可以看到docker從中央倉庫pull下來了ubuntu的鏡像;
  • bash

    我猜測是指定了運作的程式,bash體驗确實優于sh;
  • -c

    後面指定了一個運作的指令,我猜測

    c

    command

    的縮寫,而後面的語句的意思應該是向

    /data.txt

    寫入一個1~10000的随機數,然後不斷向控制台輸出

    /dev/null

    中新加入的内容;

通過

docker ps

拿到剛剛啟動的容器的id後,我們通過以下指令可以拿到

/data.txt

中的随機數:

docker exec <CONTAINER> cat /data.txt
           

之後,我們再啟動一個鏡像:

docker run -it ubuntu ls /
           
  • -it

    應當是一個差別于

    -d

    的選項,我們看到這條語句運作完成之後直接在console顯示了結果;
  • 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
           

二者似乎會産生相同的效果,我們可以看到如下效果:

Docker學習筆記(二)

我們進入了一個終端,并且打開另一個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重新整理一下就能發現資料同步過來了:

Docker學習筆記(二)

此後,我們停止并删除這兩個容器之後,再在3002啟動一個新的容器,發現這個todo還是存在的:

Docker學習筆記(二)

這裡發生了什麼事呢?

肯定是和

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

    檔案夾;
  • node:12-alpine

    是使用的鏡像名,注意這裡沒有使用我們建構的getting-started鏡像;
  • 最後是鏡像中運作的指令,這裡以熱更新的模式啟動這個項目。值得注意的是

    Apline

    并沒有bash,是以隻能用sh;

當我們在Windows上執行該指令時,會收到一個警告,因為我們将一個Windows下的檔案夾映射到了一個docker容器中,這将導緻性能較差。

此時通路

localhost:3000

并沒有顯示,通過檢視以下指令檢視運作情況:

docker container logs <CONTAINER>
           

這個指令可以檢視容器中stdout的輸出。我們可以發現這個時候正在執行

yarn install

,而我們之前先建構鏡像再直接運作的是偶,是很快就能通路

localhost:3000

的,我們看Dockerfile,發現是在Dockerfile的

RUN

語句中執行的,那麼就說明

RUN

語句中的部分是在建構鏡像的時候執行的,那我們可以合理猜測

CMD

中的内容應該是在

docker run

的時候才執行。

等了一陣子,終于結束了建構,可以在

localhost:3000

通路到我們的程式了:

Docker學習筆記(二)

同時,我們可以發現

gov-sample-app

檔案夾下出現了

mode_modules

檔案夾,這個是建構

node.js

項目産生的,檔案數目極其之多,得趕緊加一個gitignore。

然後對

src/static/js/app.js

第109行進行修改:

-                         {submitting ? 'Adding...' : 'Add Item'}
 +                         {submitting ? 'Adding...' : 'Add'}
           

儲存,然後重新整理浏覽器:

Docker學習筆記(二)

我們的更改已經生效了。通過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
           
  • -d

    表示detached模式運作,老朋友了;
  • --network

    表示這個容器需要連接配接到我們剛建立的

    todo-app

    網絡;
  • --network-alias

    表示為這個容器所在的ID起了一個“域名”,在同一個network的其它容器可以通過DNS來找到這個容器;
  • -v

    表示進行檔案系統的volume映射,注意這裡我們使用了一個名為todo-mysql-data的volume,但我們從未建立過它,事實上,docker将為我們自動建立;
  • -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學習筆記(二)

然後通過

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個重要工具:

  1. vloume/bind-mount,用于檔案的共享/持久化,前者将檔案在主控端的情況交由docker托管,而後者需要手動管理;
  2. 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

,隻不過如果目錄不存在,會自動建立。