引言
記錄docker知識的一篇文檔。
docker的安裝
docker的安裝我就不多說咸作,建議按照docker的官方安裝文檔一步一步來https://docs.docker.com/install/贸铜。需要注意的是遍蟋,Docker的windows版本只支持Win10的專業(yè)版和企業(yè)版,像我們普通學生用的Win10家庭版它是不支持的兔跌,但是docker官方也提供一個docker toolbox(下載地址:https://docs.docker.com/toolbox/toolbox_install_windows/)衡创,通過它可以在windows上運行docker帝嗡,只不過這個docker toolbox是基于一個linux虛擬機運行的,所以性能肯定是不如原生的好璃氢。
安裝好docker后哟玷,不要忘了用service docker start
或者systemctl start docker
(如果是centos7以上版本的話)啟動docker的服務
配置docker不需要sudo
剛裝好的docker每次使用docker命令都需要sudo,比較麻煩一也,可以通過以下命令省去sudo:
sudo usermod -aG docker 當前用戶名
然后一定要記得重新登錄該用戶才能生效巢寡。
docker核心概念
要理解docker,最核心的是理解三個概念椰苟,分別是:倉庫(Registry)抑月、鏡像(image)和容器(Container)。我們結(jié)合一些常見的Docker命令對這些概念進行講解舆蝴。
倉庫(Registry)
所謂倉庫谦絮,其實是個鏡像倉庫,里面有很多別人已經(jīng)打包好的鏡像洁仗,可以直接使用docker pull
命令將倉庫中的鏡像拉到本地层皱,默認的倉庫Docker的官方倉庫Docker Hub Registry。
因為墻的緣故赠潦,官方倉庫的速度會比較慢奶甘,可以配一個官方的中國加速鏡像,方法是:修改 /etc/docker/daemon.json
祭椰,加上如下的鍵值:
{
"registry-mirrors": ["https://registry.docker-cn.com"]
}
之后重啟docker服務即可生效。
使用以下命令可以去倉庫中搜索含有tutorial關(guān)鍵字的鏡像:
docker search tutorial
輸出如下:
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
learn/tutorial 38
georgeyord/reactjs-tutorial This is the backend of the React comment box… 5 [OK]
chris24walsh/flask-aws-tutorial Runs a simple flask webapp demo, with the op… 1 [OK]
......(以下省略)
可以使用docker pull命令從倉庫中拉取剛剛搜索到的鏡像到本地疲陕,這里為了避免learn/tutorial那個鏡像被取消方淤,我自己重建了一個一樣的,這里拉取我創(chuàng)建的那個鏡像:
docker pull ddfddf/tutorial
輸出如下:
Using default tag: latest
latest: Pulling from ddfddf/tutorial
271134aeb542: Pull complete
483756d37259: Pull complete
Digest: sha256:4615baf428c61a6feafd8f77f91e0cadc7f8f080710cc82362ac89a5f66a329c
Status: Downloaded newer image for ddfddf/tutorial:latest
ddfddf/tutorial
為剛剛搜索到鏡像名蹄殃,默認情況下會拉去最新版本的鏡像携茂,即ddfddf/tutorial:latest
,你也可以用:
指定某個特定版本的鏡像诅岩,比如docker pull ubuntu:14.04
讳苦。其實冒號后面的這個東西的官方術(shù)語叫做tag,這個tag可以是一串數(shù)字(比如unbantu:14.04的tag是14.04)吩谦,也可以是一個單詞(比如debian:stretch的tag是stretch)鸳谜,latest也只是一個普通的tag而已,只是當docker pull
不去專門指定tag時式廷,默認會去下載tag為latest的鏡像咐扭,通過在Docker Hub Registry中搜索你想要的鏡像,點進去之后可以查看到有哪些tag可以提供下載,如下圖:
鏡像(Image)
通過docker images
命令可以看到本地已有的鏡像:
REPOSITORY TAG IMAGE ID CREATED SIZE
ddfddf/tutorial latest 48a0196af3c3 3 hours ago 140MB
每個鏡像都有一個IMAGE ID作為唯一標識蝗肪,可以看出這個鏡像的IMAGE ID為48a0196af3c3
袜爪,使用鏡像的id可以將它刪除,命令如下:
docker rmi 鏡像id
這里先不要著急把剛剛拉下來的鏡像刪掉薛闪,待會實驗還要用辛馆。
容器(Container)
然后使用docker run來運行這個鏡像(運行之后鏡像就變成一個容器):
docker run ddfddf/tutorial apt-get install -y ping
這個命令會在docker容器中執(zhí)行"apt-get install -y ping",也就是安裝一個ping命令豁延,運行完之后容器就自動退出了昙篙。之后使用docker ps
命令可以查看所有當前正在運行的容器,輸出如下:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
空的术浪,沒有任何一個容器正在運行瓢对,之所以會這樣是因為剛剛?cè)萜鲌?zhí)行完命令后就退出了。使用docker ps -a
命令可以查看到所有容器胰苏,不管它正在運行還是已經(jīng)退出硕蛹,輸出如下:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0299878039f0 ddfddf/tutorial "apt-get install -y …" 1 minutes ago Exited (0) 11 minutes ago peaceful_wozniak
每個容器也會有一個ID作為唯一標識,從上面的輸出中可以看出CONTAINER ID
是0299878039f0
.
我們嘗試一下如下的命令:
docker run ddfddf/tutorial ping www.baidu.com
這個命令會報如下的錯誤:
docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"ping\": executable file not found in $PATH": unknown.
從"ping": executable file not found in $PATH
這句話中可以看出是因為ping命令沒有裝導致的硕并,剛剛明明裝過ping命令了尾组,為什么沒有呢碉纺?這是因為在容器中的修改并不會影響鏡像,通過docker commit
命令可以將這個容器提交成一個新的鏡像。命令如下:
docker commit 0299878039f0 ddfddf/ping
命令中0299878039f0
是之前用docker ps -a
查詢出來的容器ID邻悬。
試一下我們新提交的鏡像learn/ping
:
docker run ddfddf/ping ping www.baidu.com
輸出如下:
PING www.a.shifen.com (111.13.100.92) 56(84) bytes of data.
64 bytes from promote.cache-dns.local (111.13.100.92): icmp_req=1 ttl=127 time=60.3 ms
64 bytes from promote.cache-dns.local (111.13.100.92): icmp_req=2 ttl=127 time=61.8 ms
......(以下省略)
可見ping命令可以使用了。在上面這個現(xiàn)象的背后省艳,容器其實只是在鏡像上面添加一個可寫層瓜挽,每當對這個容器進行修改都會在可寫層標明與原本鏡像的不同之處,當你使用docker commit
命令時么库,只是提交了一個可寫層傻丝,將它變成一個不可寫的鏡像層,而這個新的鏡像和原本的鏡像共享原本鏡像的所有層诉儒,這就是所謂的docker分層機制葡缰,其實每個docker鏡像都是由好多層構(gòu)成的,這個機制能極大地縮小鏡像占用的硬盤空間忱反,如下圖:
剛剛我們運行ping命令的時候都是在前臺運行的泛释,在它運行的時候我們命令行做不了別的事情,只能眼睜睜地看它輸出温算,使用如下命令可以讓它后臺運行:
docker run -d ddfddf/ping ping www.baidu.com
這個時候在控制臺就看不到它的輸出怜校,使用docker ps
可以看到這個容器的id,它是目前唯一正在運行的容器注竿,如果想看它的輸出的話可以使用docker logs 容器ID
的方式查看韭畸。
使用docker stop
命令可以停止這個后臺運行的容器:
docker stop 2f371d67d92d
2f371d67d92d
為這個容器在我的電腦上的ID
如果你想繼續(xù)讓這個容器運行宇智,可以使用docker start
命令:
docker start 2f371d67d92d
這個容器又會從剛才停止的地方重新運行,在初學的時候總是會搞混docker start
與docker run
命令胰丁,其實在理解了容器與鏡像的區(qū)別之后随橘,就很容易理解了,docker run
使用來啟動鏡像的锦庸,而docker start
是用來重新啟動被停止的容器的机蔗。
只要鏡像被執(zhí)行了一次啊,都會生成一個新的容器甘萧,所以此時用docker -a
會看到好多廢棄的容器萝嘁,可以使用docker rm 容器ID
的方式將他們刪除掉,也可以使用如下的小技巧一次性刪除所有容器:
docker rm $(docker ps -a -q)
不用擔心扬卷,這里只是刪除所有容器而已牙言,鏡像還完好無損的保留在那里。
這里把容器和鏡像容易混淆的命令總結(jié)在了下表中:
刪除 | 啟動 | |
---|---|---|
鏡像 | docker rmi | docker run |
容器 | docker rm | docker start |
上文中一直使用容器ID來標明容器怪得,其實在啟動的時候可以通過--name選項來指定一個別名咱枉,之后就可以使用這個別名來代替容器ID使用,注意這個別名必須在本臺機器中唯一徒恋,示例如下:
docker run -d --name=test ddfddf/ping ping www.baidu.com
docker stop test
docker rm test
上面的命令中給容器起了一個叫做test的別名蚕断,然后使用它的別名將其停止與刪除。
如果有些情況下不得已要進入容器內(nèi)部進行操作的話入挣,可以使用如下命令進入容器內(nèi)部的shell:
docker run -d --name=test ddfddf/tutorial ping www.baidu.com
docker exec -ti test /bin/bash
docker exec
用于在正在運行的容器中執(zhí)行命令亿乳,-ti
選項表示分配一個虛擬終端。注意docker exec
只能在“正在運行”的容器中執(zhí)行命令径筏,所以在docker run
的時候執(zhí)行ping
就是為了讓這個容器一直運行葛假,而不是立即退出。
再聊聊倉庫(Registry)
之前講過滋恬,Docker Hub Registry是Docker的官方倉庫聊训,其實這是一個有點類似于Github的地方,任何人都可以在上面提交與下載鏡像夷恍,我們可以先去上面注冊一個賬戶(地址:https://hub.docker.com/),注冊賬戶時會有谷歌的人機認證系統(tǒng)媳维,所以需要一些科學上網(wǎng)技巧酿雪。
注冊完成后可以用如下命令在shell中登錄:
docker login -u 用戶名 -p 密碼
在將鏡像push到自己新建的賬戶之前,要用docker tag
重命名一下侄刽,將鏡像命名你的用戶名/鏡像名
這種形式指黎,不然會push認證不通過,代碼如下:
docker tag ddfddf/tutorial 你的用戶名/tutorial
docker tag
并沒有干太多的事情州丹,只是創(chuàng)建了一個到ddfddf/tutorial
鏡像的引用醋安。之后就可以使用docker push
命令將自己的鏡像推送到賬戶中去了杂彭,方便自己和別人的使用:
docker push 你的用戶名/tutorial
搞定之后就可以使用以下命令登出了:
docker logout
登錄上Docker Hub,你將能看到剛剛你push上去的鏡像吓揪。
總結(jié)
最后我再總結(jié)一下Docker的三個核心概念間的關(guān)系:
Dockerfile
如果你想要從一個基礎(chǔ)鏡像開始建立一個自定義鏡像亲怠,可以選擇一步一步進行構(gòu)建,也可以選擇寫一個配置文件柠辞,然后一條命令(docker build
)完成構(gòu)建团秽,顯然配置文件的方式可以更好地應對需求的變更,這個配置文件就是Dockerfile叭首。
學習Dockerfile的最好方式就是閱讀別人寫的Dockerfile习勤,遇到不會的指令就查一查Dockerfile的文檔,文檔地址如下:
https://docs.docker.com/engine/reference/builder/
大家遇到不知道的指令多去里面翻一翻焙格,下面我?guī)е蠹易x幾個開源Dockerfile图毕,在讀的過程中學習相關(guān)知識。
第一個Dockerfile以阿里中間件大賽給的debian-jdk8
鏡像為例眷唉,Dockerfile文件如下:
FROM debian:stretch
ARG DEBIAN_FRONTEND=noninteractive
ARG JAVA_VERSION=8
ARG JAVA_UPDATE=172
ARG JAVA_BUILD=11
ARG JAVA_PACKAGE=jdk
ARG JAVA_HASH=a58eab1ec242421181065cdc37240b08
ENV LANG C.UTF-8
ENV JAVA_HOME=/opt/jdk
ENV PATH=${PATH}:${JAVA_HOME}/bin
RUN set -ex \
&& apt-get update \
&& apt-get -y install ca-certificates wget unzip \
&& wget -q --header "Cookie: oraclelicense=accept-securebackup-cookie" \
-O /tmp/java.tar.gz \
http://download.oracle.com/otn-pub/java/jdk/${JAVA_VERSION}u${JAVA_UPDATE}-b${JAVA_BUILD}/${JAVA_HASH}/${JAVA_PACKAGE}-${JAVA_VERSION}u${JAVA_UPDATE}-linux-x64.tar.gz \
&& CHECKSUM=$(wget -q -O - https://www.oracle.com/webfolder/s/digest/${JAVA_VERSION}u${JAVA_UPDATE}checksum.html | grep -E "${JAVA_PACKAGE}-${JAVA_VERSION}u${JAVA_UPDATE}-linux-x64\.tar\.gz" | grep -Eo '(sha256: )[^<]+' | cut -d: -f2 | xargs) \
&& echo "${CHECKSUM} /tmp/java.tar.gz" > /tmp/java.tar.gz.sha256 \
&& sha256sum -c /tmp/java.tar.gz.sha256 \
&& mkdir ${JAVA_HOME} \
&& tar -xzf /tmp/java.tar.gz -C ${JAVA_HOME} --strip-components=1 \
&& wget -q --header "Cookie: oraclelicense=accept-securebackup-cookie;" \
-O /tmp/jce_policy.zip \
http://download.oracle.com/otn-pub/java/jce/${JAVA_VERSION}/jce_policy-${JAVA_VERSION}.zip \
&& unzip -jo -d ${JAVA_HOME}/jre/lib/security /tmp/jce_policy.zip \
&& rm -rf ${JAVA_HOME}/jar/lib/security/README.txt \
/var/lib/apt/lists/* \
/tmp/* \
/root/.wget-hsts
在解釋含義之前予颤,我們先體驗一下如何用Dockerfile打包一個鏡像,新建一個空目錄厢破,假設(shè)就是~/debian-jdk8
吧荣瑟,cd進這個目錄,新建一個Dockerfile
摩泪,然后把上面的內(nèi)容copy進去笆焰,然后執(zhí)行下面的命令:
docker build -t debian-jdk8:v1.0 .
其中-t debian-jdk8:v1.0
表示打包的鏡像名為debian-jdk,tag為v1.0(前面說過见坑,tag是可以任意命名的嚷掠,不一定要是這種格式),注意命令的最后有一個.
荞驴,這個表示打包的上下文(其實就是Dockerfile所在目錄)是在當前目錄不皆,然后目錄下的Dockerfile就會被編譯執(zhí)行。
執(zhí)行完畢后運行docker images
就會發(fā)現(xiàn)多了一個debian-jdk8
鏡像熊楼。
下面來解釋一下Dockerfile的結(jié)構(gòu)霹娄,那些字母全部大寫的每行第一個單詞都是Dockerfile的指令,可以看出這個Dockefile中包括的指令有FROM
鲫骗、ARG
犬耻、ENV
、RUN
执泰,下面的表格中我對其含義進行了解釋:
指令 | 含義解釋 |
---|---|
FROM |
FROM debian:stretch 表示以debian:stretch 作為基礎(chǔ)鏡像進行構(gòu)建 |
RUN | 可以看出RUN 后面跟的其實就是一些shell命令枕磁,通過&&將這些腳本連接在了一行執(zhí)行,這么做的原因是為了減少鏡像的層數(shù)术吝,每多一行RUN 都會給鏡像增加一層计济,所以這里選擇將所有命令聯(lián)結(jié)在一起執(zhí)行以減少層數(shù) |
ARG | 特地將這個指令放在RUN之后講解茸苇,這個指令可以進行一些宏定義,比如我定義ENV JAVA_HOME=/opt/jdk 沦寂,之后RUN后面的shell命令中的${JAVA_HOME} 都會被/opt/jdk 代替 |
ENV | 可以看出這個指令的作用是在shell中設(shè)置一些環(huán)境變量(其實就是export) |
多階段構(gòu)建(multi-stage build)
這個功能是在Docker17.05版本及以上才出現(xiàn)的学密,主要用于解決docker鏡像構(gòu)建的中間冗余文件的處理,這里只做簡要介紹凑队,更詳細的解釋請看官方文檔:
https://docs.docker.com/develop/develop-images/multistage-build/
有的時候會看見一些古怪则果,里面會有多個FROM
指令或者FROM...AS...
指令(這都是多階段構(gòu)建的標志),比如如下的dockerfile(來源于阿里中間件大賽的agent-demp漩氨,完整項目請見:https://github.com/DQinYuan/Agent-demo):
# Builder container
FROM registry.cn-hangzhou.aliyuncs.com/aliware2018/services AS builder
COPY . /root/workspace/agent
WORKDIR /root/workspace/agent
RUN set -ex && mvn clean package
# Runner container
FROM registry.cn-hangzhou.aliyuncs.com/aliware2018/debian-jdk8
COPY --from=builder /root/workspace/services/mesh-provider/target/mesh-provider-1.0-SNAPSHOT.jar /root/dists/mesh-provider.jar
COPY --from=builder /root/workspace/services/mesh-consumer/target/mesh-consumer-1.0-SNAPSHOT.jar /root/dists/mesh-consumer.jar
COPY --from=builder /root/workspace/agent/mesh-agent/target/mesh-agent-1.0-SNAPSHOT.jar /root/dists/mesh-agent.jar
COPY --from=builder /usr/local/bin/docker-entrypoint.sh /usr/local/bin
COPY start-agent.sh /usr/local/bin
RUN set -ex && mkdir -p /root/logs
ENTRYPOINT ["docker-entrypoint.sh"]
如果你懶得點進去我剛剛給出的完整項目地址西壮,看下面的截圖也大概知道是什么樣子了:
可以看出這是一個Java的Maven項目。
先解釋幾個之前沒解釋過的指令:
指令 | 含義解釋 |
---|---|
FROM...AS... | 這是Docker 17.05及以上版本新出來的指令叫惊,其實就是給這個階段的鏡像起個別名:FROM ...(基礎(chǔ)鏡像) AS ...(別名) 款青,在后面引用這個階段的鏡像時直接使用別名就可以了 |
COPY | 顧名思義,就是用來來回復制文件的霍狰,COPY . /root/workspace/agent 表示將當前文件夾(. 表示當前文件夾抡草,即Dockerfile所在文件夾)的所以文件拷貝到容器的/root/workspace/agent 文件夾中。通過--from 參數(shù)也可以從前面階段的鏡像中拷貝文件過來蔗坯,比如--from=builder 表示文件來源不是本地文件系統(tǒng)康震,而是之前的別名為builder的容器 |
WORKDIR | 在執(zhí)行RUN 后面的shell命令前會先cd 進WORKDIR 后面的目錄 |
ENTRYPOINT | 這個參數(shù)表示鏡像的“入口”,鏡像打包完成之后宾濒,使用docker run 命令運行這個鏡像時腿短,其實就是執(zhí)行這個ENTRYPOINT后面的可執(zhí)行文件(一般是一個shell腳本文件),也可以通過["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"] 這種方式來賦予可執(zhí)行文件的執(zhí)行參數(shù)绘梦,這個“入口”執(zhí)行的工作目錄也是WORKDIR 后面的那個目錄 |
知道上面這些指令后嘗試閱讀一下之前給出的Dockerfile橘忱。按照官方文檔的說法,多階段構(gòu)建中每一個FROM
指令表示開始一個階段卸奉,第一階段從第一個FROM
開始钝诚,在第二個FROM
之前結(jié)束,片段如下:
# Builder container
FROM registry.cn-hangzhou.aliyuncs.com/aliware2018/services AS builder
COPY . /root/workspace/agent
WORKDIR /root/workspace/agent
RUN set -ex && mvn clean package
可以看出第一階段做的事情其實就是把項目拷貝到鏡像(COPY . /root/workspace/agent
)中用maven打包(mvn clean package
)一下榄棵。
第二階段的片段如下:
# Runner container
FROM registry.cn-hangzhou.aliyuncs.com/aliware2018/debian-jdk8
COPY --from=builder /root/workspace/services/mesh-provider/target/mesh-provider-1.0-SNAPSHOT.jar /root/dists/mesh-provider.jar
COPY --from=builder /root/workspace/services/mesh-consumer/target/mesh-consumer-1.0-SNAPSHOT.jar /root/dists/mesh-consumer.jar
COPY --from=builder /root/workspace/agent/mesh-agent/target/mesh-agent-1.0-SNAPSHOT.jar /root/dists/mesh-agent.jar
COPY --from=builder /usr/local/bin/docker-entrypoint.sh /usr/local/bin
COPY start-agent.sh /usr/local/bin
RUN set -ex && mkdir -p /root/logs
ENTRYPOINT ["docker-entrypoint.sh"]
這個階段的核心就是大量的COPY
指令凝颇,從上一階段別名為builder
的鏡像中拷貝一些執(zhí)行文件過來。
這兩個階段構(gòu)建結(jié)束后疹鳄,第一階段生成的鏡像會被拋棄拧略,只有最后一個階段構(gòu)建的鏡像會被構(gòu)建成為最終的鏡像。
因為在第二階段中只是拷貝了一些必要的執(zhí)行文件過去尚辑,第一階段在maven打包過程中產(chǎn)生的大量冗余文件就被拋棄了辑鲤,所以說善于使用多階段構(gòu)建可以大大減小鏡像的大小盔腔。
更多的關(guān)于Dockerfile的最佳實踐請參考官網(wǎng)的一篇文章:
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
END
感謝閱讀