目錄
前言:
對Docker的研究,一方面來自於領導安排的研究任務,另一方面也來自於開發部署應用時遇到的實際問題,所以對於這個能解決現實痛點的工具產生了研究興趣。
應用部署可以說是軟件開發過程中最大的麻煩事:生產環境權限的限制、操作系統之間的差異、運行環境的配置、依賴庫和組件的安裝,只有這些都解決了,應用程序才能在這臺機器上運行。
例如我要部署一個Python程序:
- 程序由python3開發,而服務器上只有默認的python2,那麼先要安裝python3的環境;
- 可能服務器上有python3環境,但是版本又不合適,但升級版本可能影響其他現存的應用程序,爲了避免隱患,你只能委委屈屈的使用一個虛擬機環境;
- 然後安裝程序的依賴包,如果服務器不允許連接外網,那麼就需要繁瑣的手工安裝;
- 程序需要mysql或redis,或者需要nginx提供web服務,繼續下載安裝;
- 這臺服務器不用了,要換到別的機器上,重新來一遍吧。如果環境不一致,可能就不是簡單的來一遍了;
- 需要部署集羣服務,或者產品很好,要推廣給其他客戶,重新來好幾遍吧。
如果甲方客戶權限管理嚴格,不提供服務器的root權限,整個過程更要麻煩好幾倍。
一、Docker的基本概念
Dock是碼頭,Docker的直譯是碼頭工人。Docker將交付運行環境想象成海運,操作系統如同一個貨輪,Docker這個碼頭工人將應用及其運行環境打包成一個個標準的集裝箱,集裝箱之間不會相互影響,使用者可以用標準化手段自由組裝運行環境來最終交付。
如今,Docker已經成爲Linux最流行的應用部署工具,在很多公司被規定爲標準的應用部署方案。另外在Java等開發工程師的招聘面試中,熟悉Docker也成爲了重要的加分項。
1、容器與虛擬機
在瞭解Docker是什麼之前,首先需要理解這兩個有區別的概念:容器和虛擬機。
虛擬機,對於程序員應該都比較熟悉,比如我們常用的VMware或VirtualBox。虛擬機包含一個完整的操作系統,並模擬一整臺機器的硬件,我們知道建立虛擬機的時候要設置CPU核心數、內存大小、硬盤大小等,虛擬機一旦開啓,這些分配的資源就被全部佔用了。
容器,也是實現虛擬化的一種方式。不同的是,它不需要模擬一個完整的操作系統,更不需要模擬硬件資源,而是將應用程序、依賴關係及其運行環境打包組成一個構建塊,使之可以在隔離的進程中運行,容器之間使用沙箱機制隔離,相互之間沒有接口。同時,容器和宿主機及其他容器共享硬件資源和操作系統,這樣,服務器操作系統對應用只需要承載就可以了,而不需要對操作系統環境進行過多改造。
總體來說,容器的應用範圍更廣,一個容器包含的東西,小到一個HelloWorld程序,大到一個完整的操作系統,所以容器可以覆蓋大部分虛擬機的功能。容器不但可以在物理機上運行,同樣可以在虛擬機上運行,現在流行的雲計算解放了企業對硬件的管理,容器解放了對應用的部署和管理,所以雲計算+容器的組合已經成爲當下的一種潮流。
兩者的區別如下圖:
2、什麼是Docker
Docker 是一個開源的應用容器引擎,基於Go語言並遵從Apache2.0協議開源。
Docker 可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中,然後發佈到任何流行的Linux機器上,也可以實現虛擬化,是目前最流行的Linux容器解決方案。
Docker 的命令和使用很簡單,用戶可以方便地創建和使用容器,把自己的應用放入容器。容器還可以進行版本管理、複製、分享、修改,就像管理普通的代碼一樣。
3、鏡像、容器、倉庫
這是Docker的三個基本概念:
簡單表述三者的關係,容器是鏡像的實例,而倉庫是一類鏡像的統稱。從面向對象思想理解,可以認爲鏡像是一個類,容器是它的實例化對象,而倉庫是一組有相同名稱不同版本的類的總稱。
Image (鏡像):
Docker 鏡像可以理解爲一個特殊的文件系統,除了包含容器運行時所需的程序、庫、資源、配置等文件外,還包含了一些爲運行時準備的一些配置參數(如數據卷、環境變量、用戶等)。鏡像不包含任何動態數據,其內容在構建之後不會被改變。
鏡像內部是分層實現的,這一點在拉取鏡像時會有所體現,下載的不是單一一個文件,而是多個文件,稱爲只讀層,在邏輯上重疊在一起,並且上一層有指針指向下一層。鏡像提供了這個文件系統的統一視角,對使用者隱藏了多層的存在。參見下圖:
Container (容器):
容器就是將鏡像實例化運行起來的狀態,是動態可變化的,可以被暫停、終止、重啓,也可以將變化固態化成爲一個新的鏡像。在實現細節上就是在鏡像的只讀層上面加了一個讀寫層,參見下圖:
Repository (倉庫):
這裏注意不要把倉庫 (Repository) 和註冊服務器 (Registry) 混爲一談。註冊服務器是一個集中的存儲、分發鏡像的服務;倉庫則是一組相同名稱不同標籤(版本)的鏡像的統稱,另外倉庫在註冊服務器中還可以分組存儲。
Docker中,一個鏡像具體表示爲:<倉庫名>:<標籤>
這裏對Docker的架構原理和進程結構不做闡述,有興趣的可以自行研究。
二、Docker的應用場景
沒有一種工具是萬能的,在合適的應用場景充分發揮他的長處纔是正確的做法。Docker同樣有其應用場景及優勢劣勢。
Docker官網上給出的典型應用場景:
- Automating the packaging and deployment of applications(使應用的打包與部署自動化)
- Creation of lightweight, private PAAS environments(創建輕量、私密的PAAS環境)
- Automated testing and continuous integration/deployment(實現自動化測試和持續的集成/部署)
- Deploying and scaling web apps, databases and backend services(部署與擴展webapp、數據庫和後臺服務)
總結起來就一句話:使應用及服務的部署和管理更加方便。
1、Docker的作用
簡化配置:這是Docker公司宣傳的主要使用場景。虛擬機的最大好處是能在你的硬件設施上運行各種配置不一樣的平臺(軟件、系統),而Docker在降低了額外開銷的情況下提供了同樣的功能。它能讓你將運行環境和配置放在容器中然後部署,使同一個容器的配置可以在不同的環境中使用。
代碼流水線管理:代碼從開發者的機器到最終在生產環境上的部署,可能需要經過很多箇中間環境。而每一箇中間環境都有或大或小的差別,Docker爲應用提供了一個從開發到上線均一致的環境,簡化了代碼的流水線。
提高開發效率:顯而易見,容器免除了開發人員對不同環境繁瑣的配置和排錯,而且可以幫助開發人員快速的搭建開發環境,而且不必努力使開發環境貼近生產環境。
隔離應用:一個服務器上可能存在多個不同的應用,所依賴的運行環境不同。比如java程序有很老的應用依賴jdk1.0,新的應用依賴jdk1.8,又比如python程序開發自不同版本的解釋器和依賴庫,docker可以將應用和環境一起隔離,而不擔心相互影響。
整合服務器:docker隔離應用的能力,和共享硬件資源,有效提高了服務器的利用率,所以可以通過減少服務器的使用來降低成本。
調試能力:Docker提供了很多功能,包括可以爲容器設置檢查點、設置版本和查看兩個容器之間的差別,這些特性可以幫助調試Bug。
多租戶:對於同一個應用的鏡像,可以通過運行不同的容器來給不同的用戶使用,這個過程極其簡單,對於鏡像只不過是多實例化幾個容器,容器運行起來又是相互隔離的。這得益於容器的啓動速度快和高效的diff命令。
快速部署:在沒有虛擬機之前,購買一個物理機到安裝配置可能需要幾天時間,虛擬機將這個時間減少到分鐘級別,而創建一個容器再次將這個過程減少到秒級。
2、Docker的侷限
隔離性:與虛擬機相比,docker隔離性更弱,docker屬於進程之間的隔離,虛擬機可實現系統級別隔離。
安全性: docker 的安全性也更弱。 Docker 的租戶 root 和宿主機 root 等同,一旦容器內的用戶從普通用戶權限提升爲root權限,它就直接具備了宿主機的root權限,進而可進行無限制的操作。虛擬機租戶 root 權限和宿主機的 root 虛擬機權限是分離的,並且虛擬機利用如 Intel 的 VT-d 和 VT-x 的硬件隔離技術,這種隔離技術可以防止虛擬機突破和彼此交互,而容器至今還沒有任何形式的硬件隔離,這使得容器容易受到攻擊。
可管理性:docker 的集中化管理工具還不算成熟。各種虛擬化技術都有成熟的管理工具,例如 VMware 提供完備的虛擬機管理能力。
系統支持性:Docker是基於Linux 64bit的,對windows7的支持實際也是通過虛擬機實現的。而且Linux也不是所有版本都支持的,比如CentOS只能支持7以上版本。
網絡管理:實現方式比較簡單,主要通過命名空間來隔離。不如虛擬機有多種完善的網絡管理方式。
三、Docker安裝和使用
一般開發人員的應用開發環境是Windows,生產環境是Linux,所以介紹這兩種系統的Docker安裝。
1、Windows系統Docker安裝
Windows 7需要利用 docker toolbox 來安裝。
下載地址:
官網下載:https://download.docker.com/win/stable/DockerToolbox.exe
阿里雲鏡像下載:http://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/
Win7的安裝很簡單,按提示安裝即可:
安裝完成後,點擊 Docker QuickStart 圖標來啓動 Docker Toolbox 終端。
長這樣:
2、Linux系統Docker安裝
安裝系統版本:
CentOS Linux release 7.7.1908 (Core)
1、最好先更新一下yum程序
[root@centos7-pure ~]# yum update -y
2、查看docker已安裝舊版本,如果有則remove卸載
[root@centos7-pure ~]# yum list installed | grep docker
3、安裝依賴軟件
[root@centos7-pure ~]# yum install -y yum-utils device-mapper-persistent-data lvm2
4、添加docker的yum源
[root@centos7-pure ~]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
5、安裝docker社區版
[root@centos7-pure ~]# yum install docker-ce
6、啓動服務並加入開機自啓動
[root@centos7-pure ~]# systemctl start docker
[root@centos7-pure ~]# systemctl enable docker
7、驗證安裝成功
[root@centos7-pure ~]# docker version
3、Docker常用命令
下面從程序員熟悉的Hello World入門開始:
1、搜索鏡像
[root@centos7-pure ~]# docker search hello-world
搜索默認是官方的公有倉庫:
Docker Hub地址:https://hub.docker.com/
建立自己的容器,首先需要獲取一個原始鏡像。所以需要從公有倉庫選擇一個合適的基礎鏡像來創建自己的容器。可以search命令搜索,也可以登錄Docker Hub瀏覽。
2、拉取鏡像
[root@centos7-pure ~]# docker pull hello-world
對於鏡像,如果不指定tag,就會使用默認標籤:latest。
3、查看鏡像
[root@centos7-pure ~]# docker images
4、運行鏡像
run實際是根據鏡像創建容器,並運行容器。
[root@centos7-pure ~]# docker run hello-world
這個容器運行就是輸出這段提示,然後容器進入stop狀態,因爲這個容器是一個程序,如果是提供服務的容器,運行後就是開始提供服務,而不會自動終止。
5、查看容器
正在運行的容器:docker ps
所有容器:docker ps –a
Container_id和names都可以唯一確定一個容器,容器名可以在創建容器時指定,上面命令沒有指定容器名稱,所以是隨機生成的名字。
6、重新運行容器
這時我們想再運行一下這個容器,就不能用docker run 鏡像了,因爲這個命令會再創建一個新的容器出來。可以使用容器命令start。
[root@centos7-pure ~]# docker start -i 1d1f18e532a1
使用Container_id或names都可以指定容器,-i是容器可交互,否則默認只會返回一個Container_id。
從以上操作可以看出,Docker的命令都很規範。
命令格式都是:docker 命令 [可選項] 對象 [參數...]
命令幫助可以查看docker –help 或 docker COMMAND –help。
在明白了docker的組件關係和命令規範後,再看這張命令圖表就很清晰了,結合命令幫助,基本就可以自己寫docker命令了。
其中,Tar files是鏡像在不同機器上遷移的一種方式,即通過壓縮文件的形式對docker保存或加載鏡像;Dockerfile是創建鏡像的主要方式。
四、Docker部署python服務實戰
這裏通過一個簡單的python的web服務,使用Docker完成一個從打包服務、分發鏡像到容器部署的完整過程。
儘量模擬完整的使用場景,包括使用第三方依賴包、讀本地文件、提供http服務、寫本地文件和記錄程序日誌等。
1、python程序
server.py:
from flask import Flask, make_response
app = Flask(__name__)
@app.route('/', methods=['GET'])
def hello_world():
file = 'volume/hello.txt'
out = 'volume/out.txt'
with open(file, 'r') as f:
txt = f.read()
print(txt)
with open(out, 'w') as f:
f.write(txt)
response = make_response(txt)
return response
if __name__ == '__main__':
app.run('0.0.0.0', port=5000, debug=False, threaded=True)
代碼很簡單:引入依賴庫Flask,提供一個http服務,當請求5000端口時,程序讀取主機中hello.txt的內容”Hello World!”並返回網頁,且打印到日誌,同時將內容寫到一個新的文件out.txt中。
生成依賴文檔:
Python通過工具生成一個requirements.txt,裏面包含項目的依賴庫,本例只有一條:Flask==1.1.1
2、拉取基礎鏡像
創建python服務的鏡像,首先要有一個python環境的基礎鏡像。基礎鏡像一般選擇與開發環境相同版本,拉取一次就可以反覆使用了。我們去https://hub.docker.com/的python倉庫查找一個合適的鏡像。
我的python版本是3.6.4,選擇同樣版本,而且看3.6.4-alpine是其中最小的,所以選擇這個鏡像。
拉取到開發環境:
這個鏡像可以創建一個python容器,其功能與安裝的python環境沒有區別,可以交互、可以解釋執行py腳本等,這裏不做展開。我們需要的是把它作爲python程序的運行環境,以這個鏡像爲基礎,包裝出具體的服務鏡像。
3、創建應用的鏡像
Docker採用Dockerfile的方式創建鏡像,Dockerfile是一個文本文件,其中記錄鏡像的內容和創建步驟。
在程序目錄創建文件Dockerfile,注意沒有後綴名。
本例的Dockerfile:
FROM python:3.6.4-alpine
MAINTAINER Daniel <[email protected]>
COPY . /app
WORKDIR /app
RUN mkdir -p /app/volume
RUN pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
EXPOSE 5000
ENTRYPOINT ["/bin/sh", "-c"]
CMD ["python -u server.py >> volume/test_service.log 2>&1"]
Dockerfile步驟解析:
1)以鏡像python:3.6.4-alpine作爲基礎鏡像,如果本地沒有這個鏡像,會自動從Docker Hub拉取。
2)設置維護者的信息。
3)複製Dockerfile所在目錄的所有文件,包括server.py和requirements.txt,到容器內的虛擬目錄/app中。
4)指定容器的默認工作目錄爲/app。
RUN指令在創建鏡像時執行,相當於給基礎鏡像增加了一個改動層,然後固化爲新的只讀層,添加到新的鏡像中。
5)創建文件夾/app/volume準備作爲數據卷,即宿主機目錄掛載的位置。
6)升級pip工具,爲安裝依賴包做準備(使用清華的python庫鏡像,提高速度)。
7)從requirements.txt安裝python的第三方依賴包。
8)暴露容器的5000端口,因爲本例是一個web服務,需要暴露端口。
9) ENTRYPOINT給出程序入口命令,使容器可以表現的像一個可執行程序,本例使用sh –c作爲默認程序。本來也可以使用python命令作爲入口,但是容器內想使用重定位符獲得輸出日誌,必須在sh –c “CMD”中使用重定位。這裏踩坑無數,docker的容器日誌用起來又太麻煩。
可以有兩種形式:
ENTRYPOINT ["executable", "param1", "param2"] :推薦使用的 exec形式
ENTRYPOINT command param1 param2 :shell 形式
10)CMD給出容器啓動時默認執行的命令,如果有ENTRYPOINT,則爲ENTRYPOINT提供參數;如果沒有,則是完整的默認命令。有三種形式:
CMD ["executable","param1","param2"]:推薦使用的 exec 形式
CMD ["param1","param2"]:無可執行程序形式
CMD command param1 param2:shell 形式
本例是執行server.py腳本,並將標準輸出和錯誤輸出重定向到主機掛載目錄的test_service.log。
Dockerfile每一行以一個大寫指令開頭,主要分爲四個部分:
基礎鏡像信息指令 FROM
維護者信息指令 MAINTAINER
鏡像操作指令 RUN、EVN、ADD和WORKDIR等
容器啓動指令 CMD、ENTRYPOINT和USER等
然後從Dockerfile建立服務的鏡像:
進入程序目錄,建立鏡像:
-t是指定新的鏡像名,後面接<倉庫名>:<標籤>。注意最後有個點,表示從當前目錄的Dockerfile建立鏡像。
查看鏡像列表,發現應用的鏡像已生成:
4、上傳鏡像
向服務器上傳鏡像有兩種方式:一種是保存爲Tar文件上傳,再load爲鏡像;另一種是在服務器建立本地私有倉庫,直接push進本地倉庫,再分發。
這裏使用文件上傳方式。
將鏡像test_service:1.1.0保存爲文件test_service.tar
服務器建立程序目錄,並上傳文件test_service.tar到該目錄。
[root@centos7-pure ~]# mkdir -p ~/python/test_service
[root@centos7-pure ~]# cd ~/python/test_service
從Tar文件導入爲鏡像:
[root@centos7-pure test_service]# docker load -i test_service.tar
查看鏡像已導入:
5、運行容器
我們希望服務在後臺運行,使用主機的8333端口提供服務,能操作程序目錄中的內容,並且如果異常終止可以自動重啓:
[root@centos7-pure test_service]# docker run -d -p 8333:5000 -v /root/python/test_service:/app/volume --name docker_test_service --restart always test_service:1.1.0
-d:讓容器在後臺運行。
-p:將主機的端口映射爲容器的端口(前面是宿主機端口,後面是容器端口)
-v:掛載主機的目錄到容器的目錄,相當於在容器內建立了宿主機某個目錄的軟連接,使得容器可以讀寫主機目錄。注意:容器中用來掛載的目錄一定要是空目錄,不然目錄中原有的文件就不能讀寫了,之前對數據卷的意義不清晰,使用了/app工作目錄掛載了宿主機目錄,結果被折磨的欲仙欲死。
--name:爲容器命名。
--restart:always表示容器如果意外終止會自動重啓,保證服務可用性。
最後是鏡像名,標籤是lastest的可以省略標籤。
查看運行的鏡像:
6、測試容器
進入程序目錄,發現容器啓動後開始記錄日誌文件test_service.log。
創建程序需要讀取的hello.txt文件,添加內容“Hello World!”。
使用瀏覽器發送請求:
可以看到網頁返回預期的結果。
繼續查看out.txt寫入了內容,日誌文件也記錄了完整的日誌。整個過程如下圖:
容器完美運行!
7、建立本地倉庫
前面整個過程是通過打包文件的方式上傳服務器,簡單方便而且適用網絡隔離的情況,但是大量部署的效率比較低,而且不能集中管理項目的應用鏡像。
建立本地倉庫是對項目中鏡像進行集中版本管理的基礎,就像項目管理首先要建立SVN或Git Hub一樣。所以我們需要在服務器建立一個類似Docker Hub的本地倉庫,來管理自有的docker鏡像。
我們需要下載安裝一個Docker Registry軟件,這裏變得有意思了,既然我們有Docker了,那麼一切都可以是容器。我們直接拉取Registry的鏡像,然後運行容器就可以提供Docker註冊服務器的本地倉庫服務了。
拉取Registry鏡像:
[root@centos7-pure test_service]# docker pull registry
建立本地倉庫存儲目錄:
[root@centos7-pure test_service]# mkdir -p /opt/data/registry
運行容器:
[root@centos7-pure test_service]# docker run -d -p 5000:5000 -v /opt/data/registry:/var/lib/registry --name hl_registry registry:latest
查看運行的容器:
接下來就可以從本機的開發環境直接將創建好的應用鏡像上傳到這個本地倉庫中。
首先需要將鏡像起一個別名以匹配本地倉庫的格式:這時會發現鏡像多了一個,但是和原來的鏡像IMAGE ID相同:
上傳鏡像到本地倉庫:
Win7 docker報錯,上傳沒有成功,linux也存在同樣問題:這是因爲docker客戶端默認發送的是https請求,而搭建的本地倉庫只支持http請求。需要搭建https服務,後續繼續研究。
不過我們已經通過Tar文件上傳的方式已經將鏡像提交到了本地倉庫所在的服務器,通過本地地址上傳可以成功。
[root@centos7-pure test_service]# docker tag test_service:1.1.0 127.0.0.1:5000/test_service:1.1.0
[root@centos7-pure test_service]# docker push 127.0.0.1:5000/test_service:1.1.0
通過Registry的API接口可以查看服務器中的docker倉庫及其版本。
五、 總結與反思
1、Docker的確方便了應用的部署和版本管理,但是如何對容器資源和運行情況監控值得思考。
2、本地倉庫的搭建和使用需要繼續研究。
3、使用Compose定義和運行多容器的應用,由多個容器協作部署複雜的運行環境,需要繼續研究。
4、Docker的安全性和管理工具需要繼續改進。