Docker 鏡像操作
概念
- image 鏡像:
層疊的只讀文件系統,比如有Linux針對於docker而創建的最小的鏡像,和其他共享的鏡像。簡言之就是系統鏡像文件。利用面向對象的思想,可以認為image
就是一個類,而容器就該類的一個實例。
Docker
鏡像的層次如下:
- 最底端是一個引導文件系統,即bootfs,這很像典型的Linux/Unix的引導文 件系統。Dockei?用戶幾乎永遠不會和引導文件系統有什麽交互。實際上,當一個容器啟動 後,它將會被移到內存中,而引導文件系統則會被卸載(unmount),以留出更多的內存供 initrd磁盤鏡像使用。
- 第二層是root文件系統rootfs,它位於引導文件系統之上。rootfs可以是一種或多種操作系統(如Debian或者Ubuntu文件系統)。在傳統的Linux引導過程中,root文件系統會最先以只讀的方式加載,當引導結束並完成了完整性檢查之後,它才會被切換為讀寫模式。但是在Docker裏,root文件系統永遠只能是只讀狀態
- 在頂層則是聯合加載的多個只讀應用文件系統,雖然看似加載一個,但是已經加載了多個文件系統,所以稱之為聯合加載。
Docker將這樣的文件系統稱為鏡像。一個鏡像可以放到另一個鏡像的頂部。位於下面的鏡像稱為父鏡像(parent image),可以依次類推,直到鏡像棧的最底部,最底部的鏡像稱為基礎鏡像(base image)。最後,當從一個鏡像啟動容器時,Docker會在該鏡像的最頂層加載一個讀寫文件系統。
在Ubuntu鏡像的存放地址為
/var/lib/docker
,可以通過docker info
查看詳細信息
列出鏡像
使用docker images [image_name]
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 0458a4468cbc 3 weeks ago 112MB hello-world latest f2a91732366c 2 months ago 1.85kB
鏡像從倉庫下載下來。鏡像保存在倉庫中,而倉庫存在於Registry中。默認的Registry 是由Docker公司運營的公共Registry服務,即Docker Hub,如圖下:
搜索鏡像
使用docker search image_name
可以看到:
倉庫名
鏡像描述
用戶評價(Stars) 反應出一個鏡像的受歡迎程度;
是否官方(Official) 由上遊開發者管理的鏡像(如fedora鏡像由Fedora團隊管理);
自動構建(Automated) 表示這個鏡像是由Docker Hub的自動構建(AutomatedBuild)流程創建的。
拉取鏡像
使用docker pull image_name[:tag]
$ sudo docker pull ubuntu
Pulling repository ubuntu
c4ff7513909d: Pulling dependent layers
3db9c44f4520: Pulling dependent layers
75204fdb260b: Pulling dependent layers
可以看到拉取一系列的鏡像分層,每一層都是獨立的。拉取了ubuntu鏡像,實際上得到的是很多版本的Ubuntu操作系統,包括10.04、12.04、13.04和14.04。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 0458a4468cbc 3 weeks ago 112MB
hello-world latest f2a91732366c 2 months ago 1.85kB
雖然稱其為Ubuntu操作系統,但實際上它並不是一個完整的操作系統。它只是一個 裁剪版,只包含最低限度的支持系統運行的組件。
為了區分同一個倉庫中的不同鏡像,Docker提供了一種稱為標簽(tag)的功能。每個 鏡像在列出來時都帶有一個標簽,如12.10、12.04、quantal等。每個標簽對組成特定 鏡像的一些鏡像層進行標記(比如,標簽12.04就是對所有Ubuntu 12.04鏡像的層的標記這種機制使得在同一個倉庫中可以存儲多個鏡像。
我們可以通過在倉庫名後面加上一個冒號和標簽名來指定該倉庫中的某一鏡像,如
$ sudo docker run -t -i --name new_container ubuntu:12.04 /bin/bash
root@79e36bff89b4:/#
這個例子會從鏡像ubuntu:12.04啟動一個容器,而這個鏡像的操作系統則是Ubuntu 12.04。我們還能看到很多鏡像具有相同的鏡像ID (見鏡像ID74fe38dll401),它們被打上很多個標簽。比如,74fe38dll401的鏡像被打上了 12.04和precise兩個標簽,分 別代表該Ubuntu發布版的版本號和代號(code name)。
在構建容器時指定倉庫的標簽也是一個很好的習慣。這樣便可以準確地指定容器來源於 哪裏不同標簽的鏡像會有不同,比如Ubutnu 12.04和14.04就不一樣,指定鏡像的標簽會讓我們確切知道自己使用的是ubuntu:12.04,這樣我們就能準確知道我們在幹什麽。Docker Hub中有兩種類型的倉庫:用戶倉庫(user repository)和頂層倉庫(top-level repository)。用戶倉庫的鏡像都是由Docker用戶創建的,而頂層倉庫則是由Docker內部的 人來管理的。
用戶倉庫的命名由用戶名和倉庫名兩部分組成,如jamturl/puppet。
- 用戶名:jamturl
- 倉庫名:puppet
與之相對,頂層倉庫只包含倉庫名部分,如ubuntu倉庫。頂層倉庫由Docker公司和 由選定的能提供優質基礎鏡像的廠商(如Fedora團隊提供了 fedora鏡像)管理,用戶可 以基於這些基礎鏡像構建自己的鏡像。同時頂層倉庫也代表了各廠商和Docker公司的一種承諾,即頂層倉庫中的鏡像是架構良好、安全且最新的。
用docker run命令從鏡像啟動一個容器時,如果該鏡像不在本地,Docker會先從 Docker Hub下載該鏡像。如果沒有指定具體的鏡像標簽,那麽Docker會自動下載latest標簽。
構建Docker鏡像有以下兩種方法。
- 使用 docker commit 命令。
- 使用 docker build 命令和 Dockerfile 文件。
Docker commit
創建Docker Hub賬號
構建鏡像中很重要的一環就是如何共享和發布鏡像。可以將鏡像推送到Docker Hub或 者用戶自己的私有Registry中。為了完成這項工作,需要在Docker Hub上創建一個賬號, 可以從 https://hub.docker.com/account/signup/加入 Docker Hub
下面就可以測試剛才註冊的賬號是否能正常工作了。要登錄到Docker Hub,可以使用 docker login命令
$ sudo docker login
Username: one2inf
Password:
Email: [email protected] Login
Succeeded
這條命令將會完成登錄到Docker Hub的工作,並將認證信息保存起來以供後面使用。你的個人認證信息將會保存到$HOME/.dockercfg文件中。
用Docker的commit命令創建鏡像
創建Docker鏡像的第一種方法是使用docker commit命令。可以將此想象為是在版本控制系統裏提交變更。先創建一個容器,並在容器裏做出修改,就像修改代碼 一樣,最後再將修改提交為一個新鏡像。
先從創建一個新容器開始,這個容器基於前面己經見過的ubuntu鏡像
$ docker run --name=2commit -it ubuntu /bin/bash
root@b54765ab2a5b:/# apt-get update
......
$ apt-get install -y apache2 [--fix-missing]
啟動了一個容器,並在裏面安裝了 Apache。將這個容器作為一個Web服務器來運行,所以把它的當前狀態保存下來。這樣就不必每次都創建一個新容器並再次在裏面安裝Apache 了。為了完成此項工作,需要先使用exit命令從容器裏退出,之後再運行docker commit命令
$ sudo docker commit b54765ab2a5b one2inf/apache2
[sudo] password for whoami:
sha256:4c0be6203dec5655c2de0ed
需要註意的是,docker commit提交 的只是創建容器的鏡像與容器的當前狀態之間有差異的部分,這使得該更新非常輕量。來看看新創建的鏡像
$ docker images one2inf/apache2
REPOSITORY TAG IMAGE ID CREATED SIZE
one2inf/apache2 latest 4c0be6203dec About a minute ago 250MB
像git commit一樣可以提交一些額外的信息如
-m="A new custom image" 提交一個描述信息
--author="whoami" 提交作者
one2inf/apache2:[tag] 添加一個標簽
如
$ sudo docker commit b54765ab2a5b one2inf/apache2:webserver -m="A new custom image" --author="whoami"
這些信息可以使用docker inspect
進行查看
並不推薦使用docker commit的方法來構建鏡像。相反,推薦使用被稱為 Dockerfile的定義文件和docker build命令來構建鏡像。Dockerfile使用基本的基於DSL語法的指令來構建一個Docker鏡像,之後使用docker build命令基於該 Dockerfile中的指令構建一個新的鏡像。
第一個Dockerfile
- 啟動Docker服務
service docker start
先創建一個Dockerfile,內容如下
FROM alpine:latest
MAINTAINER whoami
CMD echo "hello docker" --指定需要執行的命令
該Dockerfile由一系列指令和參數組成。每條指令如FROM,都必須為大寫字母,且後面要跟隨一個參數Dockerfile中的指令會按順序從上到下執行,所以應該根據需要合理安排指令的順序。
參數說明
- alpine是linux針對Docker的一個極小的容器
- latest是標簽
- MAINTAINER 指定維護者的名字
- CMD command
每條指令都會創建一個新的鏡像層並對鏡像進行提交。Docker大體上按照如下流程執行Dockerfile中的指令。
- Docker從基礎鏡像運行一個容器。
- 執行一條指令,對容器做出修改。
- 執行類似docker ccmmit的操作,提交一個新的鏡像層。
- Docker再基於剛提交的鏡像運行一個新容器。
- 執行Dockerfile中的下一條指令,直到所有指令都執行完畢。
從上面也可以看出,如果你的Dockerfile由於某些原因(如某條指令失敗了)沒有正常結束,那麽你將得到了一個可以使用的鏡像。這對調試非常有幫助,可以基於該鏡像運行一個具備交互功能的容器,使用最後創建的鏡像對為什麽你的指令會失畋進行調試。
- 接著執行
docker build -t hello_docker .
使用docker命令構建一個新的容器,參數
-t
指定名字.
代表當前路徑意思是尋找當前路徑的Dockerfile,所以Dockerfile
的名字最好與約定的一樣即可
最後運行容器
[~] sudo docker run hello_docker
hello docker
第二個Dockerfile
- 先創建工作目錄HelloDocker
shell [~] cd HelloDocker [HelloDocker]
- 創建Dockerfile,其內容如下:
dockerfile FROM ubuntu MAINTAINER whoami RUN sed -i ‘s/archive.ubuntu.com/mirrors.ustc.edu.cn/g‘ /etc/apt/sources.list RUN apt-get update RUN apt-get install -y nginx COPY index.html /var/www/html ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"] EXPOSE 80
FROM ubuntu 設置基礎鏡像為 ubuntu 每個Dockerf ile的第一條指令都應該是FROM,後續指令都將基於該鏡像進行,這個鏡像被稱為基礎鏡像(base iamge)
MAINTAINER 指定維護者的名字 whoami 這條指令會告訴Docker該鏡像的作者是誰,以及作者 的電子郵件地址。這有助於標識鏡像的所有者和聯系方式。
RUN sed -i ‘s/archive.ubuntu.com/mirrors.ustc.edu.cn/g‘ /etc/apt/sources.list
RUN 是執行,這一句的意思是替換源為中國科學技術大學的源。
RUN apt-get update 安裝完成後,更新apt源
RUN apt-get install -y nginx 安裝 nginx
RUN指令會在shell裏使用命令包裝器/bin/sh -c來執行。如果是在一個不支持shell的平臺上運行或者不希望在shell中運行(比如避免shell字符串篡改),也可以使用exec格式的RUN指令
RUN [ "apt-get", " install", "-y", "nginx"]
在這種方式中,使用一個數組來指定要運行的命令和傳遞給該命令的每個參數。
COPY index.html /var/www/html 將當前路徑下的index.html拷貝至nginx的項目目錄
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"] 入口點,數組中的數據組成一條命令,作用是將nginx放置到前臺,而還是作為守護進程運行
EXPOSE 80 將容器的80端口暴露以便訪問服務器!
這條指令告訴Docker該容器內的應用程序將會使用容器的指定端口。這並不意味著可以自動訪問任意容器運行中服務的端口(這裏是80)。出於安全的原因,Docker並不會自動打開該端口,而是需要你在使用docker run運行容器時來指 定需要打開哪些端口。
創建index.html
shell [HelloDocker] echo "<b>hello the cruel woirld</b>" > index.html [HelloDocker] cat index.html <b>hello the cruel woirld</b
- 構建
sudo docker build -t whoami/hello-nginx .
命令中最後的.告訴Docker到本地目錄中去找Dockerfile文件。也可以在命令後面加上一個Git倉庫的源地址來指定Dockerfile的位置
運行容器
docker run -d -p 80:80 whoami/hello-nginx
使用curl測試
curl http://localhost
Dockerfile與image分層
Dockerfile中的每一行都產生一個新層,即第一層都有一個獨立的ID
FROM alpine:latest
-------4e38e38c8ce0
MAINTAINER whoami
--------fb1aabf4427b
CMD echo ‘hello docker‘
---53df065bfdff6
image層本身是read only,當image運行為一個容器時,會產生一個容器層(container layer),可讀可寫。分層好處:多個image時,相同的層可以共享,減輕壓力。
指令失敗時會怎樣
前面介紹了一個指令失敗時將會怎樣。下面來看一個例子:假設將軟件包的名字弄錯了,比如寫成了ngin。
再來運行一遍構建過程並看看當指令失敗時會怎樣
$ cd static一web
$ sudo docker build -t="jamtur01/static_web".
Sending build context to Docker daemon 2.56 kB
Sending build context to Docker daemon
Step 1 : FROM ubuntu:14.04 ---> 8dbd9e392a96
Step 2 : MAINTAINER James Turnbull "[email protected]"
> Running in d97e0clcf6ea
——> 85130977028d
Step 3 : RUN apt-get update
> Running in 85130977028d
——> 997485f46ec4
Step 4 : RUN apt-get install -y ngin > Running in ffcal6d58fd8
Reading package lists...
Building dependency tree...
Reading state information...
E: Unable to locate package ngin
2014/06/04 18:41:11 The command [/bin/sh -c apt-get install -y ngin] returned a non-zero code: 100
這時候需要調試一下這次失敗。我可以用docker run命令來基於這次構建到目前 為止己經成功的最後一步創建一個容器,這裏它的ID是997485f46ec4
$ sudo docker run -t -i 997485f46ec4 /bin/bash
dcgel2e59fe8:/#
這時在這個容器中可以再次運行apt-get install -y nginx,並指定正確的包名或者通過進一步調試來找出到底是哪裏出錯了。一旦解決了這個問題就可以退出容器,使用正確的包名修改Dockerfile文件,之後再嘗試進行構建。
Dockerfile和構建緩存
由於每一步的構建過程都會將結果提交為鏡像,它會將之前的鏡像層看做緩存。
然而有些時候需要確保構建過程不會使用緩存。比如apt-get update,那麽Docker將不會再次刷新APT包的緩存。這時你可能需要取得每個包的最新版本。要想略過緩存功能,可以使用docker build的--no-cache標誌
$ sudo docker build --no-cache -t=image_name
基於緩存的Dockerfile
構建緩存帶來的一個好處就是,可以實現簡單的Dockerfile模板(比如在 Dockerfile文件頂部增加包倉庫或者更新包,從而盡可能確保緩存命中)。在自己的Dockerfile文件頂部使用相同的指令集模板,比如對Ubuntu
FROM ubuntu:14.04
MAINTAINER James Turnbull "[email protected]"
ENV REFRESHED一AT 2014-07-01 RUN apt-get -yq update
首先通過FROM指令為新鏡像設置了一個基礎鏡像ubuntu: 14.04。接著使用MAINTAINER指令添加了自己的詳細信息之後又使用了一條新出現的指令ENV來在鏡像中設置環境變量。通過ENV指令來設置了一個名為REFRESHED_AT的環境變量,這個環境變量用來表明該鏡像模板最後的更新時間。最後,我使用了RUN指令來運行apt-get -yq update 命令。該指令運行時將會刷新APT包的緩存,用來確保能將要安裝的每個軟件包都更新到最新版本。
有了這個模板,如果想刷新一個構建,只需修改ENV指令中的日期。這使Docker在命 中ENV指令時開始重置這個緩存,並運行後續指令而無須依賴該緩存。也就是說,RUN apt-get update這條指令將會被再次執行,包緩存也將會被刷新為最新內容。可以擴展 此模板,比如適配到不同的平臺或者添加額外的需求。
將鏡像推送到Docker Hub
鏡像構建完畢之後,我們也可以將它上傳到Docker Hub上面去,這樣其他人就能使用這個鏡像了。比如,我們可以在組織內共享這個鏡像,或者完全公開這個鏡像。Docker Hub也提供了對私有倉庫的支持,這是一個需要付費的功能,你可以將鏡像存儲到私有倉庫中,這樣只有你或者任何與你共享這個私有倉庫的人才能訪問該鏡像。這樣你 就可以將機密信息或者代碼放到私有鏡像中,不必擔心被公開訪問了。
可以通過docker push命令將鏡像推送到Docker Hub
$ sudo docker push static_web
2013/07/01 18:34:47 Impossible to push a "root" repository.
Please rename your repository in <user>/<repo> (ex: jamturOl/ static_web)
出什麽問題了?我們嘗試將鏡像推送到遠程倉庫Static_web,但是Docker認為這是 一個root倉庫。root倉庫是由Docker公司的團隊管理的,因此會拒絕我們的推送請求。讓我們再換一種方式試一下,
$ sudo docker push jamtur01/static_web
The push refers to a repository [jamturOl/static_web] (len: 1)
Processing checksums Sending image list
Pushing repository jamtur01/static_web to registry-1.docker.io (1 tags)
自動構建
除了從命令行構建和推送鏡像,Docker Hub還允許我們定義自動構建(Automated Builds)。為了使用自動構建,我們只需要將GitHub或BitBucket中含有Dockerfile文 件的倉庫連接到Docker Hub即可。向這個代碼倉庫推送代碼時,將會觸發一次鏡像構建活 動並創建一個新鏡像。在之前該工作機制也被稱為可信構建
在Docker Hub中添加自動構建任務的第一步是將GitHub或者BitBucket賬號連接到 Docker Hub。具體操作是,打幵Docker Hub,登錄後單擊個人信息連接,之後單擊Add Repository -〉Automated Build 按鈕
有兩個選項可以選擇:Public and Private (recommended)和 Limited。選擇 Public and Private (recommended)並單擊 Allow Access 完成授權操作。有可能會被要求輸入GitHub的密碼來確認訪問請求。
完成之後在Github創建一個空項目JupyterEnv
選擇該倉庫進行自動構建
接下來的操作不言而喻。
刪除鏡像
使用docker rmi命令來刪除一個鏡像
$ sudo docker rmi image_name
Untagged: 06c6clf81534
Deleted: 06c6clf81534
Deleted: 9f551a68e60f
Deleted: 997485f4 6ec4
Deleted: al01d806d694
Deleted: 85130977028d
刪除了鏡像。在這裏也可以看到Docker的分層文件系統:每一個Deleted:行都代表一個鏡像層被刪除。
該操作與git類似只會將本地的鏡像刪除。如果之前已經將該鏡像推送到Docker Hub上,那麽它在 Docker Hub上將依然存在。如果想刪除一個Docker Hub上的鏡像倉庫,需要在登錄Docker Hub後使用Delete repository鏈接來刪除
當然也可以一次刪除多個鏡像。
私有Docker Registry
顯然,擁有Docker鏡像的一個公共的Registry非常有用。但是有時候我們可能希望構建和存儲包含不想被公開的信息或數據的鏡像。這時候我們有以下兩種選擇。
利用Docker Hub上的私有倉庫。
在防火墻後面運行你自己的Registry。
值得感謝的是,Docker公司的團隊開源@了他們用於運行Docker Registry的代碼,這樣我們就可以基於此代碼在內部運行自己的Registry。
從容器運行Registry
從Docker容器安裝一個Registry非常簡單。
$ sudo docker run -p 5000:5000 registry
該命令將會啟動一個運行Registry應用的容器,並綁定到本地宿主機的5000端口。
測試新 Registry
- 通過docker images命令來找到鏡像ID
接著,我們找到鏡像ID,即22d47c8cb6e5,並使用新的Registry給該鏡像打上標簽。為了指定新的Registry目的地址,需要在鏡像名前加上主機名和端口前綴。在這個例子裏我們的Registry主機名為docker.example.com,
$ sudo docker tag 22d47c8cb6e5 docker.example.com:5000/jamtur01/static_web
為鏡像打完標簽之後,就能通過docker push命令將它推送到新的Registry中去了
$ sudo docker push docker.example.com:5000/jamtur01/static_web The push refers to a repository [docker.example.com:5000/jamturOl /static_web] (len: 1)
Processing checksums Sending image list
Pushing repository docker.example.com:5000/jamturOl/static_web (1 tags) Pushing 22d47c8cb6e556420e5d58ca5cc376ef18e2de93b5cc90e868albbc8318clc Buffering to disk 58375952/? (n/a)
Pushing 58.38 MB/58.38 MB (100%)
這個鏡像就被提交到了本地的Registry中,並且可以將其用於使用docker run命令構建新容器
$ sudo docker run -t -i docker.example.com:5000/jamturOl/static_web /bin/bash
Docker 鏡像操作