我的碎碎念:Docker入門指南
之前曾經翻譯過很多Docker入門介紹的文章,之所以再翻譯這篇,是因為Anders的角度很獨特,思路也很調理。你也可以看下作者的演講稿 《Docker, DevOps的未來》。本文介紹了Docker的一些基本概念、誘人的特性、Docker的工作原理、日常管理基本操作,以及一些Docker的問題的解決方案。
什么是Docker,你應該知道些什么?
相比很多人的解釋,我相信說Docker是一個輕量級的虛擬機更容易理解。另外一種解釋是:Docker就是操作系統中的chroot
。如果你不知道 chroot
是什么的話,后一種解釋可能無法幫助你理解什么是Docker。 chroot是一種操作,能改變當前運行的進程和子進程的根目錄。 程序運行在這樣的一個被修改的環境中,它不能訪問這個環境目錄樹之外的文件和命令,這個被修改的環境就是“chroot牢籠”。
-- Arch Linux 的 wiki 中對 chroot 的解釋
虛擬機 vs. Docker
下面這張圖描述了虛擬機和Docker之間的差異。 在VM中,宿主OS上是 hypervisor(虛擬機監視器), 最上層是客戶機操作系統,而Docker則使用 Docker引擎和容器。 這樣解釋你能理解嗎? Docker引擎和hypervisor之間的區別又是什么呢?你可以列出運行在宿主OS上的進程來理解它們的區別。下面這個簡單的進程樹可以看出它們的差異。雖然虛擬機中運行了很多進程,但是運行虛擬機的宿主機上卻只有一個進程。
# Running processes on Host for a VM $ pstree VM-+= /VirtualBox.app |--= coreos-vagrant</pre>
而運行Docker引擎的主機上則可以看到所有的進程。 容器進程是運行在宿主OS上的!,他們可以通過普通的ps
,kill
等命令進行檢查和維護。# Docker在主機中的進程 $ pstree docker-+= /docker |--= /bin/sh |--= node server.js |--= go run app |--= ruby server.rb ... |--= /bin/bash</pre>
所有的東西都是透明的, 意味著什么呢?意味著Docker容器比虛擬機更小,更快,更容易與其它東西集成。如下圖所示。
安裝CoreOS的小型虛擬機居然有1.2GB, 而裝上busybox的小型容器只有2.5MB。最快的虛擬機啟動時間也是分鐘級的,而容器的啟動時間通常不到一秒。在同一宿主機上安裝虛擬機需要正確的設置網絡, 而安裝Docker非常簡單。
這么來看,容器是輕量、快速并且易集成,但這并不是它的全部!
Docker 是一份合約
Docker還是開發者和運維之間的“合約”。 開發和運維在選擇工具和環境時的姿態通常差別很大。開發者想要使用一些閃亮的新東西,比如Node.js、Rust、Go、微服務、Cassandra、 Hadoop、blablabla.........而運維則傾向于使用以往用過的工具,因為事實證明那些舊的工具很有效。
但這恰恰是Docker的亮點, 運維喜歡它,因為Docker讓他們只要關心一件事: 部署容器, 而開發者也一樣很開心,只要寫好代碼,然后往容器里一扔,剩下的交給運維就完事了。
不過別急,這還沒完。運維還能幫助開發者構建優化好的容器以便用于本地開發。
更好的資源利用
很多年前,那時候還沒有虛擬化,當我們需要創建一個新服務時,我們必須申請實際的物理機硬件。 這可能要花上數月,依賴于公司的流程。一旦服務器到位,我們創建好服務,很多時候它并沒有像我們希望的那樣成功,因為服務器的CPU使用率只有5%。 太奢侈了。
接著,虛擬化來了。它可以在幾分鐘之內把一臺機器運轉起來,還可以在同一硬件上運行多個虛擬機,資源使用率就不只5%了。但是,我們還需要給每個服務分配一個虛擬機,因此我們還是不能如愿的使用這臺機器。
容器化是演化進程的下一步。容器可以在幾秒之內創建起來,而且還能以比虛擬機更小的粒度進行部署。
依賴
Docker啟動速度真的很酷。 但是,我們為什么不把所有的都服務部署到同一臺機器上呢? 原因很簡單:依賴的問題。在同一臺機器上安裝多個獨立的服務,不管是真是機器還是虛擬機都是一場災難。用Docker公司的說法是:地獄一樣的矩陣依賴。
而Docker通過在容器中保留依賴關系解決了矩陣依賴的問題。
速度
快當然不錯,但是能快100倍就太不可思議了。速度讓很多事情成為可能,增加了更多新的可能性。比如,現在可以快速創建新的環境,如果需要從 Clojure開發環境完整的切換到Go語言嗎?啟動一個容器吧。需要為集成和性能測試提供生產環境DB ?啟動一個容器吧! 需要從Apache切換整個生產環境到Nginx?啟動容器吧!
Docker是怎么工作的?
Docker是一個Client-Server結構的系統,Docker守護進程運行在主機上, 然后通過Socket連接從客戶端訪問, 客戶端和守護進程也可以運行再同一主機上,但這不是必須的。Docker命令行客戶端也是類似的工作方式,但它通常通過Unix域套接字而不是TCP套接 字連接。
守護進程從客戶端接受命令并管理運行在主機上的容器。
Docker 概念及相互作用
- 主機, 運行容器的機器。
- 鏡像,文件的層次結構,以及包含如何運行容器的元數據
- 容器,一個從鏡像中啟動,包含正在運行的程序的進程
- Registry, 鏡像倉庫
- 卷,容器外的存儲
- Dockerfile, 用于創建鏡像的腳本
我們可以通過Dockerfile
來構建鏡像, 還可以通過commit
一個運行的容器來創建一個鏡像,這個鏡像可以會被標記,可以推到Registry或者從Registry上拉下來,可以通過創建或者運行鏡像的方式來啟動容器,可以被stop
,也可以通過rm
來移除它。
鏡像
鏡像是一種文件結構,包含如何運行容器的元數據。Dockerfile中的每條命令都會在文件系統中創建一個新的層次結構,文件系統在這些層次上構建起來,鏡像就構建于這些聯合的文件系統之上。
當容器啟動后,所有鏡像都會統一合并到一個進程中。 聯合文件系統中的文件被刪除時, 它們只是被標記為已刪除,但實際上仍然存在。
# Commands for interacting with images $ docker images # 查看所有鏡像. $ docker import # 從tarball創建鏡像 $ docker build # 通過Dockerfile創建鏡像 $ docker commit # 從容器中創建鏡像 $ docker rmi # 刪除鏡像 $ docker history # 列出鏡像的變更歷史
鏡像大小
這是一些經常使用的鏡像相關的數據: </li> - scratch - 基礎鏡像, 0個文件,大小為0
- busybox - 最小Unix系統,2.5MB,10000個文件
- debian:jessie - Debian最新版, 122MB, 18000 個文件
- ubuntu:14.04 - 188MB,23000 個文件 </ul>
創建鏡像
可以通過docker commit container-id
、 docker import url-to-tar
或者 docker build -f Dockerfile .
來創建鏡像。 先看commit的方式:
# 通過commit的方式來創建鏡像 $ docker run -i -t debian:jessie bash root@e6c7d21960:/# apt-get update root@e6c7d21960:/# apt-get install postgresql root@e6c7d21960:/# apt-get install node root@e6c7d21960:/# node --version root@e6c7d21960:/# curl https://iojs.org/dist/v1.2.0/iojs-v1.2.0- linux-x64.tar.gz -o iojs.tgz root@e6c7d21960:/# tar xzf iojs.tgz root@e6c7d21960:/# ls root@e6c7d21960:/# cd iojs-v1.2.0-linux-x64/ root@e6c7d21960:/# ls root@e6c7d21960:/# cp -r * /usr/local/ root@e6c7d21960:/# iojs --version 1.2.0 root@e6c7d21960:/# exit $ docker ps -l -q e6c7d21960 $ docker commit e6c7d21960 postgres-iojs daeb0b76283eac2e0c7f7504bdde2d49c721a1b03a50f750ea9982464cfccb1e
從上面可以看出,我們可以通過
docker commit
來創建鏡像,但是這種方式有點凌亂而且很難復制, 更好的方式是通過Dockerfile來構建鏡像,因為它步驟清晰并且容易復制: FROM debian:jessieDockerfile for postgres-iojs
RUN apt-get update RUN apt-get install postgresql RUN curl https://iojs.org/dist/iojs-v1.2.0.tgz -o iojs.tgz RUN tar xzf iojs.tgz RUN cp -r iojs-v1.2.0-linux-x64/* /usr/local</pre>
然后用下面的命令來構建:$ docker build -tag postgres-iojs .
Dockerfile中的每一個命令都創建了新版的layer,通常把類似的命令放在一起,通過&&和續行符號把命令組合起來:
FROM debian:jessieDockerfile for postgres-iojs
RUN apt-get update && \ apt-get install postgresql && \ curl https://iojs.org/dist/iojs-v1.2.0.tgz -o iojs.tgz && \ tar xzf iojs.tgz && \ cp -r iojs-v1.2.0-linux-x64/* /usr/local</pre>
這些行中命令的順序很重要,因為Docker為了加速鏡像的構建,會緩存中間的鏡像。 組織Dockerfile的順序時,注意把經常變化的行放在文件的底部,當緩存中相關的文件改變時,鏡像會重新運行,即使Dockerfile中的行沒有發生變化也是如此。Dockerfile 中的命令
Dockerfile 支持13個命令, 其中一些命令用于構建鏡像,另外一些用于從鏡像中運行容器,這是一個關于命令什么時候被用到的表格:
BUILD 命令:
- FROM - 新鏡像是基于哪個鏡像的
- MAINTAINER - 鏡像維護者的姓名和郵箱地址
- COPY - 拷貝文件和目錄到鏡像中
- ADD - 同COPY一樣,但會自動處理URL和解壓tarball壓縮包
- RUN - 在容器中運行一個命令, 比如:
apt-get install
- ONBUILD - 當構建一個被繼承的Dockerfile時運行命令
- .dockerignore - 不是一個命令, 但它能控制什么文件被加入到構建的上下文中,構建鏡像時應該包含.git以及其它的不需要的文件。 </ul>
- CMD - 運行容器時的默認命令,可以被命令行參數覆蓋
- ENV - 設置容器內的環境變量
- EXPOSE - 從容器中暴露出端口, 必須顯式的通過在主機上的RUN命令帶上-p或者-P來暴露端口
- VOLUME - 指定一個在文件系統之后的存儲目錄。如果不是通過
docker run -v
設置的, 那么將被創建為/var/lib/docker/volumes
- ENTRYPOINT - 指定一個命令不會被
docker run image cmd
命令覆蓋。常用于提供一個默認的可執行程序并使用命令作為參數。
</ul>
- USER - 為RUN、CMD、ENTRYPOINT命令設置用戶
- WORKDIR - 為RUN、CMD、ENTRYPOINT、ADD、COPY命令設置工作目錄 </ul>
RUN 命令:
BUILD, RUN命令都有的命令:
運行的容器
容器啟動后,進程在它可以運行的聯合文件系統中獲得了新的可寫層。從1.5版本起,它還可以讓最頂層的layer設置為只讀,強制我們為所有文件輸出(如日志、臨時文件)使用卷。
# 用于與容器交互的命令 $ docker create # 創建一個容器,但不啟動它 $ docker run # 創建并啟動一個容器 $ docker stop # 停止容器 $ docker start # 啟動容器 $ docker restart # 重啟容器 $ docker rm # 刪除容器 $ docker kill # 給容器發送kill信號 $ docker attach # 連接到正在運行的容器中 $ docker wait # 阻塞直到容器停止為止 $ docker exec # 在運行的容器中執行一條命令
docker run
如上所述,docker run
是用戶啟動新容器的命令, 這里是一些通用的運行容器的方法: # 交互式運行容器 $ docker run -it --rm ubuntu
這是一個可以讓你像普通的終端程序一樣交互式的運行容器的方法, 如果你想把管道輸出到容器中,可以使用-t選項。
- --interactive (-i) - 將標準輸入發送給進程
- -tty (-t) - 告訴進程有終端連接。 這個功能會影響程序的輸出和它如何處理Ctrx-C等信號。
- --rm - 退出時刪除鏡像。
# 后臺運行容器 $ docker run -d hadoop
docker run -env
# 運行一個命名容器并給它傳一些環境變量 $ docker run \ --name mydb \ --env MYSQL_USER=db-user \ -e MYSQL_PASSWORD=secret \ --env-file ./mysql.env \ mysql
</li> - --name - 給容器命名, 否則它是一個隨機容器
- --env (-e)- 設置容器中的環境變量
- --env-file - 從env-file中引入所有環境變量(同Linux下的source env-file 功能)
- mysql - 指定鏡像名為 mysql:lastest </ul>
docker run -publish
# 發布容器的80端口到主機上的隨機端口 $ docker run -p 80 nginx發布容器端口80和主機上的8080端口
$ docker run -p 8080:80 nginx
發布容器80端口到主機127.0.0.0.1的8080端口
$ docker run -p 127.0.0.1:8080:80 nginx
發布所有容器中暴露的端口到主機的隨機端口上
$ docker run -P nginx</pre>
nginx 鏡像,比如暴露出80和443端口。FROM debian:wheezy MAINTAINER NGINX "docker-maint@nginx.com"EXPOSE 80 443</pre>
docker run --link
# 啟動postgres容器,給它起名為mydb $ docker run --name mydb postgres把mydb 鏈接到 myqpp 的db
$ docker run --link mydb:db myapp</pre>
連接容器需要設置容器到被連接的容器之間的網絡,有兩件事要做:
- 通過容器的連接名,更新 /etc/hosts 。 在上面的例子中,連接名是db, 可以方便的通過名字db來訪問容器。
- 為暴露的端口設置環境變量。這個好像沒啥實際用處,你也可以通過
主機名:端口
的形式訪問對應的端口。
</ul>
docker run limits
還可以通過run limits來限制容器可以使用的主機資源# 限制內存大小 $ docker run -m 256m yourapp限制進程可以使用的cpu份數(cpu shares)(總CPU份數為1024)
$ docker run --cpu-shares 512 mypp
改變運行進程的用戶為www,而不是root(出于安全考慮)
$ docker run -u=www nginx</pre>
設置CPU份數為1024中的512份并不意味著可以使用一半的CPU資源,這意味著在一個無任何限制的容器中,它最多可以使用一半的份數。比如我們有兩 個有1024份的容器,和一個512份的容器(1024:1024:512) ,那么512份的那個容器,就只能得到1/5的總CPU份數docker exec container
docker exec
允許我們在已經運行的容器內部執行命令,這點在debug的時候很有用。
# 使用id 6f2c42c0在容器內部運行shell $ docker exec -it 6f2c42c0 sh
卷
卷提供容器外的持久存儲。 這意味著如果你提交了新的鏡像,數據將不會被保存。
# Start a new nginx container with /var/log as a volume $ docker run -v /var/log nginx
如果目錄不存在,則會被自動創建為:/var/lib/docker/valumes/ec3c543bc..535
實際的目錄名可以通過命令:docker inspect container-id
找到。
# 啟動新的nginx容器,設置/var/log為卷,并映射到主機的/tmp目錄下 $ docker run -v /tmp:/var/log nginx
還可以使用--valumes-from
選項從別的容器中掛載卷。
# 啟動容器db $ docker run -v /var/lib/postgresql/data --name mydb postgres啟動backup容器,從mydb容器中掛載卷
$ docker run --volumes-from mydb backup</pre>
Docker Registry
Docker Hub是Docker的官方鏡像倉庫,支持私有庫和共有庫,倉庫可以被標記為 官方倉庫,意味著它由該項目的維護者(或跟它有關的人)策劃。
Docker Hub 還支持自動化構建來自Github和Bitbucket的項目,如果啟用自動構建功能,那么每次你提交代碼到代碼庫都會自動構建鏡像。
即使你不想用自動構建,你還是可以直接docker push
到Docker Hub,Docker pull則會拉取鏡像下來。docker run
一個本地不存在的鏡像,則會自動開始docker pull
操作。
你也可以在任意地方托管鏡像,官方有 Registry的開源項目,但是,還有很多Bug。
此外,Quay、Tutum和Google 還提供私有鏡像托管服務。
檢查容器
檢查容器的命令有一大把:
$ docker ps # 顯示運行的容器 $ docker inspect # 顯示容器信息(包括ip地址) $ docker logs # 獲取容器中的日志 $ docker events # 獲取容器事件 $ docker port # 顯示容器的公開端口 $ docker top # 顯示容器中運行的進程 $ docker diff # 查看容器文件系統中改變的文件 $ docker stats # 查看各種緯度數據、內存、CPU、文件系統等
下面詳細講一下docker ps
和docker inspect
,這兩個命令最常用了。
# 列出所有容器,包括已停止的。 $ docker ps --all CONTAINER ID IMAGE COMMAND NAMES 9923ad197b65 busybox:latest "sh" romantic_fermat fe7f682cf546 debian:jessie "bash" silly_bartik 09c707e2ec07 scratch:latest "ls" suspicious_perlman b15c5c553202 mongo:2.6.7 "/entrypo some-mongo fbe1f24d7df8 busybox:latest "true" db_dataInspect the container named silly_bartik
Output is shortened for brevity.
$ docker inspect silly_bartik 1 [{ 2 "Args": [ 3 "-c", 4 "/usr/local/bin/confd-watch.sh" 5 ], 6 "Config": { 10 "Hostname": "3c012df7bab9", 11 "Image": "andersjanmyr/nginx-confd:development", 12 }, 13 "Id": "3c012df7bab977a194199f1", 14 "Image": "d3bd1f07cae1bd624e2e", 15 "NetworkSettings": { 16 "IPAddress": "", 18 "Ports": null 19 }, 20 "Volumes": {}, 22 }]</pre>
技巧花招
獲取容器id。寫腳本時很有用。
# Get the id (-q) of the last (-l) run container獲取最后(-l)一個啟動的容器id(-q)
$ docker ps -l -q c8044ab1a3d0</pre>
docker inspect
可以帶格式化的字符串----Go語言模板作為參數,詳細描述所需的數據。寫腳本時同時有用。$ docker inspect -f '{{ .NetworkSettings.IPAddress }}' 6f2c42c05500 172.17.0.11
使用docker exec
來跟運行中的容器進行交互。
# 獲取容器環境變量 $ docker exec -it 6f2c42c05500 envPATH=/usr/local/sbin:/usr... HOSTNAME=6f2c42c05500 REDIS_1_PORT=tcp://172.17.0.9:6379 REDIS_1_PORT_6379_TCP=tcp://172.17.0.9:6379 ...</pre>
通過卷來避免每次運行時都重建鏡像, 下面是一個Dockerfile,每次構建時,會拷貝當前目錄到容器中。1 FROM dockerfile/nodejs:latest 2 3 MAINTAINER Anders Janmyr "anders@janmyr.com" 4 RUN apt-get update && \ 5 apt-get install zlib1g-dev && \ 6 npm install -g pm2 && \ 7 mkdir -p /srv/app 8 9 WORKDIR /srv/app 10 COPY . /srv/app 11 12 CMD pm2 start app.js -x -i 1 && pm2 logs 13
構建并運行鏡像:
$ docker build -t myapp . $ docker run -it --rm myapp
為避免重建,創建一次性鏡像并在運行時掛載本地目錄。
安全
大家可能聽說過使用Docker不那么安全。這不是假話,但這不成問題。
目前Docker存在以下安全問題:
- 鏡像簽名未被正確的核準。
- 如果你在容器中擁有root權限,那你潛在的擁有對真個主機的root權限。 </ul>
- 從你的私有倉庫中使用受信任的鏡像
- 盡量不要以root運行容器
- 把容器中的root當作是主機上的root? 還是把容器的根目錄設置為容器內的根目錄 ? </ul>
安全解決辦法:
如果服務器上所有的容器都是你的,那你不需要擔心他們之間會有危險的交互。
“選擇”容器
我給選擇兩字加了引號, 因為目前根本沒有任何別的選擇, 但是很多容器愛好者想玩玩,比如Ubuntu的LXD、微軟的Drawbridge,還有 Rocket。Rocket由CoreOS開發,CoreOS是一個很大的容器平臺。 他們開發Rocket的理由是覺得Docker公司讓Docker變得臃腫,并且還和CoreOS有業務沖突。
他們在這個新的容器中,嘗試移除那些因為歷史原因而留下來的Docker瑕疵。并通過 socket activation提供簡單的容器和徹底的安全構建。
編排
當我們把應用程序拆開到多個不同的容器中時,會產生一些新的問題。怎么讓不同的部分進行通信呢? 這些容器在單個主機上怎么辦? 多個主機上又是怎么處理?單個主機上,Docker通過連接來解決編排的問題。
為簡化容器的鏈接操作,Docker提供了一個叫
docker-compose
的工具。(以前它叫 fig
, 由另一家公司開發,然后最近Docker收購了他們) docker-compose
docker-compose
在單個 docker-compose.yml
文件中聲明多個容器的信息。來看一個例子,管理web和redis兩個容器的配置文件: 1 web: 2 build: . 3 command: python app.py 4 ports: 5 - "5000:5000" 6 volumes: 7 - .:/code 8 links: 9 - redis 10 redis: 11 image: redis
啟動上述容器,可以使用
docker-compose up
命令 $ docker-compose up Pulling image orchardup/redis... Building web... Starting figtest_redis_1... Starting figtest_web_1... redis_1 | [8] 02 Jan 18:43:35.576 # Server started, Redis version 2.8.3 web_1 | * Running on http://0.0.0.0:5000/
也可以通過detached模式(detached mode)啟動:
docker-compose up -d
,然后可以通過 docker-compose ps
查看容器中跑了啥東西: $ docker-compose up -d Starting figtest_redis_1... Starting figtest_web_1... $ docker-compose psName Command State Ports
figtest_redis_1 /usr/local/bin/run Up figtest_web_1 /bin/sh -c python app.py Up 5000->5000</pre>
還可以同時讓命令在一個容器或者多個容器中同時工作。# 從web容器中獲取環境變量 $ docker-compose run web env擴展到多個容器中(Scale to multiple containers)
$ docker-compose scale web=3 redis=2
從所有容器中返回日志信息
$ docker-compose logs</pre>
從以上命令可以看出,擴展很容易,不過應用程序必須寫成支持處理多個容器的方式。在容器外,不支持負載均衡。Docker托管
很多公司想做在云中托管Docker的生意,如下圖。
這些提供商嘗試解決不同的問題, 從簡單的托管到做"云操作系統"。其中有兩家比較有前景:
CoreOS
如上圖所示,CoreOS是可以在CoreOS集群中托管多個容器的一系列服務的集合:
- CoreOS Linux發行版是裁剪的Linux,在初次啟動時使用114MB的RAM,沒有包管理器, 使用Docker或者它自己的Rocket運行一切程序。
- CoreOS 使用Docker(或Rocket)在主機上安裝應用。
- 使用
systemd
作為init服務,它的性能超級好,還能很好的處理啟動依賴關系, 強大的日志系統,還支持socket-activation。 etcd
是分布式的,一致性 K-V 存儲用于配置共享和服務發現。fleet
,集群管理器,是systemd
的擴展,能與多臺機器工作,采用etcd
來管理配置并運行在每一個臺CoreOS服務器上。
</ul>
- Elastic Beanstalk部署Docker容器,它工作的很好,但就是太慢了, 一次全新的部署需要好幾分鐘,感覺跟一般的容器秒級啟動不大對勁。
- ECS、Elastic Container Server是Amazon上游容器集群解決方案, 目前還在預覽版3,看起來很有前途,跟Amazon其它服務一樣,通過簡單的web service調用與它交互。 </ul>
- Docker is here to stay
- 解決了依賴問題
- 容器各方面都很快
- 有集群解決方案,但不能無縫對接 </ul>
AWS
Docker容器托管在Amazon有兩種途徑:總結
原文鏈接:A Not Very Short Introduction to Docker(翻譯:何林沖 校對:宋瑜)
=============================
譯者介紹
何林沖, 目前就職于騰訊計算機系統有限公司, 負責游戲自動化運維體系架構設計及開發工作, 熱愛開源技術。希望通過翻譯技術文章為社區貢獻微薄之力。
來自:http://dockerone.com/article/277