Docker的誕生
我們總是會遇到測試對開發說項目又不work了,開發總說:在我電腦上是ok的阿。
項目組加了新人,我們就需要教新人配置各種開發環境,每換一台機器就要配置一次,每來一個新人就要配置一次。
於是我們想,有什么辦法可以在安裝軟件的時候把環境也安裝過來?一摸一樣復制過來就沒這么多問題了。
於是,我們開始用虛擬機,它自己一套系統,然后你在里面配置好環境,復制給隊友就好了。根本上虛擬機也是一個文件。
但是有個缺點就是太大了!啟動太慢!一些系統的操作完全是多余的。
於是就開始用linux容器。Linux 容器不是模擬一個完整的操作系統,而是對進程進行隔離。容器里面的應用,直接就是底層系統的一個進程,操作系統使用宿主的操作系統。
Docker 就是 Linux 容器的一種封裝。
Docker 做什么
- web應用的自動化打包測試
- 微服務
- 提供開發環境
安裝docker應用
mac 安裝地址https://store.docker.com/editions/community/docker-ce-desktop-mac
通過dmg安裝,打開這個應用就可以了。
通過docker -v
來測試有沒有安裝成功。
第一個docker應用
docker的強大之處,一句開啟一個nginx服務。
docker run -d -p 80:80 --name webserver nginx
然后打開 http://localhost 看一下,如果沒有問題應該就可以看到nginx的歡迎頁了。
docker image
docker的核心概念之一,image,image其實就是鏡像。一個容器是由1個或多個image組成的,比如我們nginx的應用,就是下載了docker提供的nginx鏡像。
一個web應用可以有多個image,比如nginx,python,db等等,我們可以自由組合image從而生成我們自己的iamge!發布我們自己的image,大家就可以直接下載我們的鏡像,從而直接在同一個環境下開發和獲取服務啦。
我們可以看一下我們當地的鏡像。
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest b175e7467d66 3 weeks ago 109MB
可以看到每個鏡像的一些信息,也可以通過docker image rm nginx
來刪除這個鏡像。
docker container
docker的核心概念之二,container,container是docker的容器,我們所有運行的程序都需要container來承載。
運行一個程序需要做什么?首先需要從docker hub上面找到我們需要的image文件(也可以根據自己的需求自己構造image文件),然后通過docker run命令生成一個容器來供程序運行,然后我們的程序就可以運行在docker上了。
我們第一個應用的docker container run
命令會從 image 文件,生成一個正在運行的容器實例,我們就可以通過ip來訪問這個服務。
我們第一個命令的各個參數含義如下:
-d container做為守護進程在后台運行
-p 本機80端口映射container80短褲
--name 容器的名字叫做webserver。
為什么我們只有run命令而沒有拉取nginx的image文件呢?
docker會首先找本地nginx image,如果沒有就自動從docker hub上面下載。
當然也可以自己首先下載到本地:docker image pull library/nginx
其中library是 image 文件所在的組,由於 Docker 官方提供的 image 文件,都放在library組里面,所以它的是默認組,可以省略。
image 文件生成的容器實例,本身也是一個文件,稱為容器文件。
也就是說,一旦容器生成,就會同時存在兩個文件: image 文件和容器文件。
而且關閉容器並不會刪除容器文件,只是容器停止運行而已。
# 列出本機正在運行的容器
$ docker container ls
# 列出本機所有容器,包括終止運行的容器
$ docker container ls -a
我們可以看到我們正在運行的nginx服務。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0b559b5addf8 nginx "nginx -g 'daemon of…" 13 days ago Up 2 seconds 0.0.0.0:80->80/tcp webserver
當然我們也可以通過start,stop,restart來控制服務的情況。
比如
docker container stop webserver
我們通過ps命令來看一下現在的容器情況:
docker ps -a
會看到,我們的webserver的status:Exited (0) About a minute ago。我們再運行docker container start webserver
就可以重啟服務。
可以簡寫為
docker start webserver
和docker stop webserver
,docker rm webserver
。rm命令顧名思義就是刪除這個container了。
容器通信
關於容器和image的基礎操作其實就幾個命令。還有容器的通信也是很重要的,因為我們需要知道容器的情況,debug什么的,就需要對容器進行操作。
這只講一個命令就是docker exec
。這個命令就允許你進入容器。
比如docker exec -it webserver bash
。
-it參數:容器的 Shell 映射到當前的 Shell,然后你在本機窗口輸入的命令,就會傳入容器。
bash:容器啟動以后,內部第一個執行的命令。這里是啟動 Bash,保證用戶可以使用 Shell。
這樣我們就進入到了webserver的容器內部,我們可以通過修改nginx.conf來操作nginx服務,或者查看nginx的log。
docker log
docker的log有個命令docker logs webserver
,我們可以看到docker的這個服務的訪問情況。
有時候會遇到
docker start xxx
然后看一下docker ps -a
,這個xxx還是沒有啟動。這時候可以看一下docker logs xxx
,他很可能報錯了。然后你的容器就啟動不了了。
生成自己的image文件
之前提到,你進行了一些操作,容器啟動不了,然后你進入這個bash,他會告訴你需要先啟動容器才能進入bash。那怎么辦呢,此時就需要一些比較麻煩的辦法了,比如接下來要說的生成自己的image文件。
docker run -d -p 80:80 --name webserver nginx
,后面指定了nginx做為image,它啟動就會首先執行nginx xxx來開啟這個nginx服務。但是因為一些原因開啟不了。那我們只能以bash的方式‘開啟’這個容器。然后修改這個container的內容。再以nginx的方式重啟。
我們怎么以bash的方式啟動我們這個nginx container呢?我們需要把當前的container生成為一個我們自己的image文件。
我們通過run命令把image文件生成一個container文件,我們也可以通過docker commit
來生成我們的image文件:
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
-a :提交的鏡像作者;
-c :使用Dockerfile指令來創建鏡像;
-m :提交時的說明文字;
-p :在commit時,將容器暫停。
docker commit -a 0b559b5addf8 temp
,我們生成一個臨時的temp image。可以通過docker image ls
查看我們生成的image。
docker rm webserver
我們刪掉沒用的container。
docker run -it --name webserver -p 80:80 -d temp
。我們根據我們生成的temp來生成一個container容器。
ok,現在我們可以docker exec -it webserver bash
來修改我們的配置了。
修改完之后還需要再次生成一個iamge,因為我們需要通過nginx的方式啟動這個iamge。
docker commit -a 0c5a9b5asddf8 temp2
。我們再次根據改完之后的container生成了一個temp2。
docker rm webserver
我們刪掉沒用的container。
docker run -it --volumes-from temp2 --name webserver -p 80:80 -d nginx
。
這樣就完成了。--volumes-from 參數是可以允許我們從另一個容器當中掛載容器中已經創建好的數據卷。
- 0b559b5addf8是我們webserver container的id
- 具體的命令可以看https://jiajially.gitbooks.io/dockerguide/content/chapter_fastlearn/docker_run/--volumes-from.html
構建自己的image
跟之前不同的構建自己的image,我們可以自由組合image來達到我們想要的效果。比如,第一個例子,我們通過docker exec -it webserver bash
進入容器之后,發現這個bash什么都沒有!
vi都沒有!我們就自己構建一個帶vim的image。
很簡單就是下載一個vim就可以了。apt-get install vim
,當然你也可以在當前container自己安裝vim。但是比較懶,就自己生成一個image吧。
docker提供了docker build
命令。
我們需要寫一份配置文件Dockerfile:
FROM openresty/openresty:trusty
RUN apt-get update && apt-get install -y vim
一共就兩行。涉及到了兩條指令,FROM 和 RUN
FROM 指定基礎鏡像,RUN 執行命令。也可以FROM scratch不指定基礎鏡像,就是說你沒有依賴。
上面的就是依賴openresty鏡像,然后進入這個鏡像安裝vim。
再比如一份dockerfile
FROM debian:jessie
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
Dockerfile 中每一個指令都會建立一層,RUN 也是。每一個 RUN 的行為,新建立一層,在其上執行這些命令,執行結束后,commit 這一層的修改,構成新的鏡像。上面的這種寫法,創建了 7 層鏡像。
正確寫法應該是這樣:
FROM debian:jessie
RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
最后添加了清理工作的命令,刪除了為了編譯構建所需要的軟件,清理了所有下載、展開的文件,並且還清理了 apt 緩存文件。
這是很重要的一步,我們之前說過,鏡像是多層存儲,每一層的東西並不會在下一層被刪除,會一直跟隨着鏡像。因此鏡像構建時,一定要確保每一層只添加真正需要添加的東西,任何無關的東西都應該清理掉。
apt-get install -y vim
不要省略-y,安裝過程中會有一些bash的交互,如果沒有-y,安裝就會不成功。-y默認全選yes
構建
接下來是我們的構建命令:
docker build openresty .
我們構建了一個叫openresty的image。后面這個點值的是當前路徑。還可以通過-f指定dockerfile的路徑。
需要注意的是,docker並不是真的通過這個.來指定dockerfile的路徑的。這其實是在指定上下文路徑。
首先我們要理解 docker build 的工作原理。Docker 在運行時分為 Docker 引擎(也就是服務端守護進程)和客戶端工具。Docker 的引擎提供了一組 REST API,被稱為 Docker Remote API,而如 docker 命令這樣的客戶端工具,則是通過這組 API 與 Docker 引擎交互,從而完成各種功能。
雖然表面上我們好像是在本機執行各種 docker 功能,但實際上,一切都是使用的遠程調用形式在服務端(Docker 引擎)完成。
docker build 命令構建鏡像,其實並非在本地構建,而是在服務端,也就是 Docker 引擎中構建的。
如何才能讓服務端獲得本地文件呢?
這就引入了上下文的概念。當構建的時候,用戶會指定構建鏡像上下文的路徑,docker build 命令得知這個路徑后,會將路徑下的所有內容打包,然后上傳給 Docker 引擎。這樣 Docker 引擎收到這個上下文包后,展開就會獲得構建鏡像所需的一切文件。
COPY 這類指令中的源文件的路徑都是相對路徑。
這就是為什么 COPY ../package.json /app 或者 COPY /opt/xxxx /app 無法工作的原因。
構建好openresty之后,我們就可以通過docker run -d -p 80:80 --name test openresty
來開啟一個openresty(nginx+lua)的服務了。
docker-compose
使用一個 Dockerfile 模板文件,可以讓用戶很方便的定義一個單獨的應用容器。
然而,在日常工作中,經常會碰到需要多個容器相互配合來完成某項任務的情況。
例如要實現一個 Web 項目,除了 Web 服務容器本身,往往還需要再加上后端的數據庫服務容器,甚至還包括負載均衡容器等。
Compose 恰好滿足了這樣的需求。它允許用戶通過一個單獨的 docker-compose.yml 模板文件(YAML 格式)來定義一組相關聯的應用容器為一個項目(project)。
Compose 中有兩個重要的概念:
- 服務 (service):一個應用的容器,實際上可以包括若干運行相同鏡像的容器實例。
- 項目 (project):由一組關聯的應用容器組成的一個完整業務單元,在 docker-compose.yml 文件中定義。
Compose 的默認管理對象是項目,通過子命令對項目中的一組容器進行便捷地生命周期管理。
mac安裝的docker里面應該自帶docker-compose。
下面我們用 Python 來建立一個能夠記錄頁面訪問次數的 web 網站。
新建文件夾,在該目錄中編寫 app.py 文件
from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
count = redis.incr('hits')
return 'Hello World! 該頁面已被訪問 {} 次。\n'.format(count)
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)
編寫 Dockerfile 文件,內容為
FROM python:3.6-alpine
ADD . /code
WORKDIR /code
RUN pip install redis flask
CMD ["python", "app.py"]
編寫 docker-compose.yml 文件,這個是 Compose 使用的主模板文件。
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
運行docker-compose up
。我們的服務就起來了。
docker-compose是針對某個項目的,只能在docker-compose.yml的工作目錄下運行。
docker-compose up -d # 在后台運行
docker-compose ps
#輸出如下
Name Command State Ports
--------------------------------------------------------------------------------
docker_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp
docker_web_1 python app.py Up 0.0.0.0:5000->5000/tcp
我們可以通過docker-compose stop
停止這兩個服務。通過docker-compose start
開啟這兩個服務。
也可以知道單個服務: docker-compose start web
。
我們可以通過docker-compose來管理我們的docker服務
docker-machine
我們有多台機器,如果都要安裝docker,不能分別ssh到機器上,然后install docker,太麻煩了,就需要有個安裝工具。
docker-machine可以在一台機器上通過命令控制幾台機器安裝docker環境。但是不能指定docker版本。
我們需要驅動來選擇我們控制的主機。
Docker Machine 支持多種后端驅動,包括虛擬機、本地主機和雲平台等。
這里使用xhyve。
xhyve 是 macOS 上輕量化的虛擬引擎,使用其創建的 Docker Machine 較 VirtualBox 驅動創建的運行效率要高。
首先安裝一下xhyve
brew install docker-machine-driver-xhyve
使用create命令創建主機實例。
docker-machine create \
-d xhyve \
# --xhyve-boot2docker-url ~/.docker/machine/cache/boot2docker.iso \
--engine-opt dns=114.114.114.114 \
--engine-registry-mirror https://registry.docker-cn.com \
--xhyve-memory-size 2048 \
--xhyve-rawdisk \
--xhyve-cpu-count 2 \
test
最簡單的create命令docker-machine create -d xhyve --xhyve-boot2docker-url ~/.docker/machine/cache/boot2docker.iso test
。
boot2docker是一個最小的linux系統,加--xhyve-boot2docker-url ~/.docker/machine/cache/boot2docker.iso參數避免每次都從外網下載boot2docker.iso。
如果首次下載比較慢,可以手動下載boot2docker.iso放到~/.docker/machine/cache/boot2docker.iso,然后指定如上參數就可以
如果創建成功,通過docker-machine ls
可以看到,我們已經有一個主機處於runing狀態了。
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
test - xhyve Running tcp://193.111.32.3:2376 v17.06.0-ce
我們可以通過
docker-machine ip test
指定test主機來查看他的ip,
docker-machine env test
查看環境變量。
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://193.111.32.3:2376"
export DOCKER_CERT_PATH="/Users/chenby/.docker/machine/machines/test"
export DOCKER_MACHINE_NAME="test"
# Run this command to configure your shell:
# eval $(docker-machine env test)
通過docker-machine start test
,docker-machine stop test
來開啟和關閉這個主機。
主機通信
在執行docker-machine env test
命令的時候可以看到后
# Run this command to configure your shell:
# eval $(docker-machine env test)
你可以執行eval $(docker-machine env test)
。然后docker ps -a
。你就會發現,打印出來的是空。
因為此時你通過設置env變量,已經連接了test主機的docker。test主機只有一個docker其他都沒有。
除了這種方式,docker-machine還提供了ssh命令進入主機。
docker-machine ssh test
。發現進入了一個新的交互界面,這就是test主機的環境。
我們在test環境中執行docker run -d -p 80:80 --name webserver nginx
來創建一個nginx server。
docker-machine ip test
,使用這個ip訪問80端口。可以發現已經找到nginx歡迎頁面。
docker-swarm docker集群
docker 1.12 之后,docker swarm成為docker的一個子命令。它可以把多個docker主機組成的系統轉換為單一的虛擬docker主機,使得容器可以組成跨主機的子網網絡。
docker swarm就是docker的集群工具。
首先,我們創建3個xhyve虛擬主機。
docker-machine create -d xhyve --xhyve-boot2docker-url ~/.docker/machine/cache/boot2docker.iso test
docker-machine create -d xhyve --xhyve-boot2docker-url ~/.docker/machine/cache/boot2docker.iso worker1
docker-machine create -d xhyve --xhyve-boot2docker-url ~/.docker/machine/cache/boot2docker.iso worker2
可以看一下現在有3個虛擬主機。
docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
test - xhyve Running tcp://193.111.32.5:2376 v17.06.0-ce
worker1 - xhyve Running tcp://193.111.32.6:2376 v17.06.0-ce
worker2 - xhyve Running tcp://193.111.32.7:2376 v17.06.0-ce
首先,我們進入test,把它當作manager主機,只有manager主機才能控制其他主機。
docker-machine ssh test
。
然后執行swarm init 操作,創建一個集群。
docker swarm init --advertise-addr 193.111.32.5
init完之后會打印出
Swarm initialized: current node (kju71ai4gwjd7omujk3wxu52e) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-3p6t8whnzh4q1cv743qe0sov5jhylm3559ktbbvx0zkprjv0je-6zeju5ownlhll8j7m3g99e3ia 193.111.32.5:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
說明test主機已經成為了manager,成功創建了一個集群。
docker node ls #查看一下當前的集群,只有一個test。
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
kju71ai4gwjd7omujk3wxu52e * test Ready Active Leader
我們再進入worker1主機,ssh worker1。
復制一下剛剛init之后打印出來的命令,
docker swarm join --token SWMTKN-1-3p6t8whnzh4q1cv743qe0sov5jhylm3559ktbbvx0zkprjv0je-6zeju5ownlhll8j7m3g99e3ia 193.111.32.5:2377
# This node joined a swarm as a worker.
然后進入worker2,同樣的操作。
最后回到ssh test,因為只能在test里面操作子主機。
docker node ls
##
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
7z7ceaabs2t195vhdgr8vyw25 worker1 Ready Active
kju71ai4gwjd7omujk3wxu52e * test Ready Active Leader
tqq0zaw3viz12xs4dd4azflo9 worker2 Ready Active
可以看到我們的test節點是一個Leader的狀態。
docker info
來查看現在的集群信息。
現在我們的集群創建完了,怎么在集群上跑我們的服務呢?
這里還是以nginx為例。
我們在manager主機上運行:
docker service create -p 80:80 --name webserver nginx
我們看一下服務情況。
docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
ilero5rmrm2b webserver replicated 1/1 nginx:latest *:80->80/tcp
REPLICAS 表示有多少服務的實例在跑。
docker service ps webserver
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
nrxl4obz83e2 webserver.1 nginx:latest test Running Running 7 seconds ago
可以看到現在一個實例跑在test上。
我們也可以
docker service scale webserver=5
拓展到5個worker,
執行
docker service ps webserver
可以看到,我們有5個實例,分別跑在不同的節點上。
我們訪問193.111.32.5,193.111.32.6,193.111.32.7,都能看到nginx的服務,但是其實不一定打到的是當前的節點。
這就是swarm做的調度。
比較有意思的是,當你kill掉一個docker 實例。swarm會自動幫你生成一個新的實例來保證5個server。
比如:
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
xqr3dfk34k4w webserver.1 nginx:latest worker1 Running Running 3 minutes ago
k471q80g91wj \_ webserver.1 nginx:latest test Shutdown Shutdown 3 minutes ago
test上面的webserver.1服務掛掉了,自動在worker1上面起了一個webserver.1 的服務。當然在哪起服務要看swarm的算法了。
這里有篇比較好的swarm api的文章:https://www.digitalocean.com/community/tutorials/how-to-create-a-cluster-of-docker-containers-with-docker-swarm-and-digitalocean-on-ubuntu-16-04