Docker學習筆記(一)
暑期加入了沃天宇老師的實驗室進行暑期的實習。在正式開始工作之前,師兄先讓我了解一下技術棧,需要了解的有docker、k8s、springboot、springcloud。
謹以一系列博客記錄一下自己學習的筆記。更多內容見Github
2021/7/5
主要內容為跟隨官方文檔的Get-Start過一遍流程,加上一些自己的探索和思考。
參考資料
- docker官網: https://www.docker.com/
- 菜鳥教程: https://www.runoob.com/docker/docker-tutorial.html
- docker中文社區: https://www.docker.org.cn/
什么是docker?
在開始學習之前,先連帶猜測給docker下一個定義,深入學習之后再回頭來驗證一下。
docker的作用應當是解決程序的環境問題,通過將環境和程序一起打包,使得能夠方便地在不同計算機上恢復程序所需要的環境並運行程序。
這將解決開發過程中的環境不一致問題,大大降低部署時的環境問題。
相比於虛擬機,docker應當具有較高的效率和較低的代價。
安裝
Linux參考:https://docs.docker.com/engine/install/
Windows參考:https://docs.docker.com/docker-for-windows/install/
注意Windows安裝的時候需要打開WSL2功能,並且升級至最新版本。
運行官方demo
下載
首先,下載官方的示例app,將倉庫中多余部分刪除,保留app文件夾,並重命名為gov-sample-app
。
Dockerfile
創建Dockerfile
:
# syntax=docker/dockerfile:1
FROM node:12-alpine
RUN apk add --no-cache python g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
- 第一句,
FROM
語句制定了初始的image,由於本地沒有這個image,所以會從倉庫中下載; - 第二句,
RUN
,應當是指定了一些需要使用的包,到這一步應當也是下載安裝,作用應當是作為第一句引入的image的補充,添補額外的應用; - 第三句,看起來應當是指定一個運行目錄,但是這個目錄是容器內的目錄還是
Build
然后切換到gov-sample-app
文件夾下運行:
docker build -t getting-started .
這個命令通過剛剛創建的Dockerfile來創建一個docker鏡像。其中-t
指令為該鏡像指定一個tag,我們運行這個鏡像的時候可以通過這個tag來指代這個鏡像。
此時先開始下載所需要的依賴,包括node:12-alpine
鏡像和后面的python g++ make
。但是此時發現下載速度感人,需要進行換源。
因為我是在自己的阿里雲服務器上進行實驗,所以換了阿里的源https://cr.console.aliyun.com/cn-beijing/instances/mirrors。
重試發現仍不奏效,應當是第二步中apk add
的問題。簡單搜索之后了解到,這里使用的鏡像Alpine
其實是一個Linux發行版,因為其具有輕量化的優點,所以經常作為docker容器中使用的OS。因此,造成目前卡頓的原因是Alpine的包管理工具apk從倉庫下載速度過慢,應當對apk進行換源。
在第一句FROM
后再加一行以進行換源:
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
雖然還是有些慢,但是好多了。最后Build成功,以下為輸出:
第一次Biuld:
$ sudo docker build -t getting-started .
Sending build context to Docker daemon 4.659MB
Step 1/7 : FROM node:12-alpine
---> deeae3752431
Step 2/7 : RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
---> Running in bb1b2b2fca8d
Removing intermediate container bb1b2b2fca8d
---> 306dcbb9fb1b
Step 3/7 : RUN apk add --no-cache python g++ make
---> Running in b9c56ec12a6b
fetch http://mirrors.aliyun.com/alpine/v3.11/main/x86_64/APKINDEX.tar.gz
fetch http://mirrors.aliyun.com/alpine/v3.11/community/x86_64/APKINDEX.tar.gz
(1/21) Installing binutils (2.33.1-r1)
(2/21) Installing gmp (6.1.2-r1)
(3/21) Installing isl (0.18-r0)
(4/21) Installing libgomp (9.3.0-r0)
(5/21) Installing libatomic (9.3.0-r0)
(6/21) Installing mpfr4 (4.0.2-r1)
(7/21) Installing mpc1 (1.1.0-r1)
(8/21) Installing gcc (9.3.0-r0)
(9/21) Installing musl-dev (1.1.24-r3)
(10/21) Installing libc-dev (0.7.2-r0)
(11/21) Installing g++ (9.3.0-r0)
(12/21) Installing make (4.2.1-r2)
(13/21) Installing libbz2 (1.0.8-r1)
(14/21) Installing expat (2.2.9-r1)
(15/21) Installing libffi (3.2.1-r6)
(16/21) Installing gdbm (1.13-r1)
(17/21) Installing ncurses-terminfo-base (6.1_p20200118-r4)
(18/21) Installing ncurses-libs (6.1_p20200118-r4)
(19/21) Installing readline (8.0.1-r0)
(20/21) Installing sqlite-libs (3.30.1-r2)
(21/21) Installing python2 (2.7.18-r0)
Executing busybox-1.31.1-r10.trigger
OK: 212 MiB in 37 packages
Removing intermediate container b9c56ec12a6b
---> 30167547cd72
Step 4/7 : WORKDIR /app
---> Running in 28935dd6ee9b
Removing intermediate container 28935dd6ee9b
---> 9b4f5c2f993f
Step 5/7 : COPY . .
---> 9e6b8013cc6f
Step 6/7 : RUN yarn install --production
---> Running in ffcc047905f0
yarn install v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
info There appears to be trouble with your network connection. Retrying...
info fsevents@1.2.9: The platform "linux" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 93.45s.
Removing intermediate container ffcc047905f0
---> 4f85bb916dc2
Step 7/7 : CMD ["node", "src/index.js"]
---> Running in 05444490086d
Removing intermediate container 05444490086d
---> 302e7c6b161a
Successfully built 302e7c6b161a
Successfully tagged getting-started:latest
到此,我們知道了,docker是需要一個os的,而一個常用的選擇就是輕量的Linux發行版Alpine
。我們通過Dockerfile
來指定一個鏡像如何打包,其中FROM
指令來指定一個初始的鏡像,類似於OOP中的繼承,我們在這個鏡像的基礎上,通過一系列指令來豐富其內容,創建我們的鏡像。
但是,仍舊有遺留的問題:
RUN
和CMD
有什么區別,它們的參數看起來都是在Alpine
中運行的指令,這里的示例中它們采用了不同的格式,前者為shell格式,后者為exec格式,但是我試驗后發現,將其格式對調,沒有任何報錯;COPY
到底拷貝了什么;WORKDIR
是哪兒來的?目前來看,官方包里的示例代碼就是在app
文件夾下,但是我這里將其改名為gov-sample-app
,但是Build階段並沒有報錯;- Build結束后應當有一個image,但是我並沒有在文件夾中看到新的文件,這個鏡像保存在哪里了?
- 在試驗遺留問題(1)的時候,我發現此時執行Build時,讓我之前頭疼的
apk add
的過程被跳過了,直接Using cache
,並且可以發現,我只改動了Step6和7,對於前面的5步,除FROM
和COPY
外,都直接Using cache
了(從windows的log來看,FROM也直接使用了本地的cache中的內容),看來docker似乎是對Dockerfile中的大部分步驟都一步一步地進行了cache,這是怎么實現的?
第二次Build:
sudo docker build -t getting-started .
Sending build context to Docker daemon 4.659MB
Step 1/7 : FROM node:12-alpine
---> deeae3752431
Step 2/7 : RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
---> Using cache
---> 306dcbb9fb1b
Step 3/7 : RUN apk add --no-cache python g++ make
---> Using cache
---> 30167547cd72
Step 4/7 : WORKDIR /app
---> Using cache
---> 9b4f5c2f993f
Step 5/7 : COPY . .
---> fb639ed69103
Step 6/7 : RUN ["yarn", "install", "--production"]
---> Running in 3bd8d313084f
yarn install v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.9: The platform "linux" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 61.84s.
Removing intermediate container 3bd8d313084f
---> 9435838e03d2
Step 7/7 : CMD node src/index.js
---> Running in 49c60121400a
Removing intermediate container 49c60121400a
---> 7cb5bd6154b2
Successfully built 7cb5bd6154b2
Successfully tagged getting-started:latest
Run
docker run -dp 3000:3000 getting-started
其中-d
指令表示這個鏡像放到后台去執行,此時不會進入到容器中,而是會直接detached,類似於screen的detached。
-p
表示要指定端口的映射,后面3000:3000
表示將宿主機的3000端口映射到容器的3000端口。經過試驗,前面的是宿主機的端口號,后面的是容器的端口號。
最后,通過build時指定的tag名稱來指定了需要運行的鏡像的名字——getting-started
。可見,build打包好的鏡像應該是保存在了一個docker的倉庫里的,我們可以通過構建時指定的標簽來指定它。
訪問3000端口即可看到這個程序已經啟動起來了:

通過docker ps
命令可以查看已經啟動的容器,然后通過docker stop <CONTAINER ID>
來關閉已經detached的容器。
更新源碼
按照官方文檔,將src/static/js/app.js
文件第56行進行修改:
- <p className="text-center">No items yet! Add one above!</p>
+ <p className="text-center">You have no todo items yet! Add one above!</p>
此時再執行build
,查看3000端口,發現更改沒有生效。再執行run
,獲得如下報錯:
docker: Error response from daemon: driver failed programming external connectivity on endpoint hardcore_colden (b2a0229cf98dca6b7a0f58a654b8263ee6e4718629116598128d1a2c633b733e): Bind for 0.0.0.0:3000 failed: port is already allocated.
報錯顯示宿主機的3000端口已經被占用,改用3001:
docker run -dp 3001:3000 getting-started
可以看到更改生效了,而此時訪問3000仍舊可以看到舊的未更改的app。

此時,我們有以下這些命令:
構建一個鏡像:
docker build -t <NAME>
運行一個鏡像:
docker run -dp <HOST_PORT>:<CONTAINER_PORT> <NAME>
停止一個容器(但這個容器仍舊存在):
docker stop <CONTAINER>
刪除一個容器(必須已停止):
docker rm <CONTAINER>
停止並刪除一個容器:
docker rm -r <CONTAINER>
查看正在運行的:
docker ps
查看所有容器(包括已停止但未刪除的):
docker ps -a
指定<CONTAINER>
的方式有三種:
CONTAINER ID
的全稱;CONTAINER ID
的前綴子串,長度只要能夠區分不同的容器即可;NAMES
,即容器的名字;
目前看來我已經啟動的容器都有一個自然語言的NAME,但是目前不知道這個是怎么指定的,也不知道怎么生成的。
分享/發布鏡像
首先,注冊一個DockerHub賬號,登錄並創建一個新的倉庫:

然后通過
docker login
來登錄自己的DockerHub賬號。在通過
docker tag getting-started <USER_ID>/getting-started
來創建一個應該與DockerHub對應的鏡像,這個鏡像是getting-started的復制。再通過
docker push <USER_ID>/getting-started
來將本地的鏡像同步至DockerHub倉庫。然后在另一台機器上(我在阿里雲的自己的服務器上)登錄后執行
docker pull <USER_ID>/getting-started
來將遠程倉庫的鏡像同步下來。這一系列操作與git差不多,只不過git可以通過git push -u
來創建本地與遠程倉庫的關聯,而docker通過docker tag
來創建一個鏡像的復制表示對遠程倉庫的引用。
最后在服務器上執行run,來啟動容器運行同步下來的進項。並且通過ssh的端口轉發實現訪問。

這里我將從遠程倉庫同步下來的鏡像啟動在3001端口,將未修改源碼時build的鏡像啟動在3000端口。
通過docker images
可以查看目前所有的鏡像。這里可以看到每個鏡像都有一個TAG,默認值為latest
,我們可以通過在鏡像名后面加:<tagname>
(注意冒號)來指定TAG。
小結
至此,我們簡單嘗試了docker的鏡像構建、運行,以及遠程倉庫的同步、拉取的基本操作。
下一篇:Docker學習筆記(二):https://www.cnblogs.com/SnowPhoenix/p/14979696.html