行百里er · 程式 ·

容器化技術之Docker-從入地到上天


0x01 容器化技術

1.1 歷史演化

這裡簡單BB一下容器化技術的發展過程,覺得「太長不看」的可直接下滑看實戰部分。

1.1.1 物理機時代

物理機時代,當我們的程序開發完成後,需要部署到伺服器上,如果項目體量不大,部署在單台機器上也還可以,但是如果部署集群架構的項目,就很難了。

物理機時代的限制主要在以下幾點:

  • 「部署非常慢」 每個主機都要安裝作業系統、相關的應用程式所需要的環境,各種配置
  • 「成本很高」 伺服器的價格是很貴滴,我們項目上的兩台HP伺服器,據說將近10萬塊
  • 「資源浪費」 有時候就為了水平擴展一下應用就需要加一台伺服器,太浪費了
  • 「難於擴展和遷移」 比如代碼倉庫遷移、資料庫遷移等都需要考慮一大堆配置
  • 「受制於硬體」

1.1.2 虛擬化時代

虛擬化時代具有以下特點:

  • 「多部署」
  • 「資源池」
  • 「資源隔離」
  • 「很容易擴展」
  • 「VM需要安裝作業系統」

每一台虛擬機都必須安裝作業系統,才能在虛擬機上做其他的事情。

虛擬機的監視器 ( 「hypervisor」 ) 是類似於用戶的「應程序運行在主機OS之上」,如 VMware 的 workstation,這種虛擬化產品提供了的硬體,像我們在機器上面安裝一個linux的虛擬機就是:

圖中的虛擬機都能夠獨立運行,都安裝了作業系統,但是他們都依賴於我自己的物理機器,我這台物理機斷電了,他們都得掛。

1.1.3 容器化時代

虛擬化是物力資源層面的隔離,那麼容器可以看做是是APP層面的隔離。

容器化技術的應用場景:

  • 「標準化的遷移方式」 開發環境打包給運維,運維展開後就能得到相同的環境
  • 「統一的參數配置」 運行程序相關的參數,在打包的時候就能進行設置
  • 「自動化部署」 進行鏡像還原的過程是自動化部署的,不需要人工參與。我們項目上就是使用通過Gitlab的CICD功能感知代碼的提交,使用Docker進行構建鏡像。全程自動化。
  • 「應用集群監控」 提供了應用監控功能,實時了解集群的運行狀況
  • 「開發與運維之間的溝通橋樑」 因為標準化的環境部署方式,可以減少不必要的環境不一致導致的問題世界清淨了不少,程式設計師專心搞開發!

0x02 Docker入門

2.1 Docker介紹

「Docker」 是一個開源的應用容器引擎,基於 「Go 語言」開發並遵從 Apache2.0 協議開源。

「Docker」 可以讓開發者「打包應用以及依賴包到一個輕量級、可移植的容器中」,然後「發布到任何流行的 Linux 機器上」「也可以實現虛擬化」

容器是完全使用「沙箱機制」,相互之間不會有任何接口,更重要的是容器「性能開銷極低」

  • 開源的應用容器引擎,基於 Go 語言開發
  • 容器是完全使用沙箱機制,容器開銷極低
  • 容器化技術的代名詞
  • 一定的虛擬化職能
  • 標準化的應用打包

2.2 CentOS 7下安裝Docker

由於伺服器90%以上都是Linux系統的,所以我們在CentOS虛擬機上安裝Docker,這些安裝命令可以當做手冊來參考。

  1. 「安裝基礎包」
yum install -y yum-utils device-mapper-persistent-data lvm2

「device-mapper-persistent和lvm2」 安裝數據存儲驅動包,進行數據存儲用的

「yum-utils」 安裝工具包,簡化安裝

  1. 「設置安裝源」
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

「yum-utils」 提供的「yum-config-manager」簡化工具,用於修改yum的安裝源

「--add-repo」 設置安裝源

  1. 「將軟體包信息提前在本地緩存一份,用來提高搜索安裝軟體的速度」
yum makecache fast
  1. 「安裝Docker社區版」
yum -y install docker-ce
  1. 「啟動Docker」
service docker start

以上是docker的安裝步驟,匯總如下:

yum install -y yum-utils device-mapper-persistent-data lvm2yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repoyum makecache fastyum -y install docker-ceservice docker start
  1. 「驗證Docker安裝」

docker -version

2.3 拉取docker鏡像

使用 「docker pull」 拉取hello-world鏡像:

docker pull hello-world

使用 「docker images」 查看鏡像:

2.4 根據鏡像啟動容器

「docker run 鏡像id<:tag>」 命令

docker run hello-world

2.5 配置鏡像加速

國內網從docker中央倉庫下載鏡像的速度我是不能忍的,我們可以通過鏡像加速器來進行加速。

使用阿里雲的容器鏡像加速服務就很方便。

登錄阿里雲,搜索「容器鏡像服務」,找到鏡像加速器:

2.6 Docker安裝目錄

Docker安裝目錄位於var/lib/docker,進去看一下:

cd /var/lib/docker && ll

這裡面有image、containers、volumes等目錄,是關於鏡像、容器等相關的目錄,下面來看一下Docker的這些重要的概念。

2.7 基本概念

這個圖中包含了docker容器、鏡像、倉庫等重要概念。

Docker客戶端通過「docker run」命令啟動容器,Docker Server通過「docker daemon」守護進程查看本地有沒有鏡像,如果沒有則到「docker倉」庫中通過「docker pull」拉取到本地,然後執行「docker run」創建容器。

「docker daemon」守護進程,「管理鏡像和容器」

2.7.1 鏡像

鏡像是文件,是只讀的,提供了運行程序完整的軟硬體資源,是應用程式的「「貨櫃」」。

2.7.2 容器

是鏡像的實例,由Docker負責創建,容器之間彼此隔離。

2.7.3 倉庫

倉庫(Repository)是集中存放鏡像的地方。

2.7.4 數據卷

數據卷是在宿主機中可以在容器之間進行共享和重用的一系列的文件和文件夾。

2.7.5 網絡

Docker網橋是宿主機虛擬出來的,並不是真實存在的網絡設備,外部網絡是無法尋址到的。這也代表著外部網絡無法通過直接Container-IP訪問到容器。

0x03 Docker快速部署Tomcat

  1. 「docker pull tomcat:8.5-jdk8-openjdk」

:8.5-jdk8-openjdk表示鏡像的tag,如果不加:8.5-jdk8-openjdk默認拉取最新的。

  1. 「docker run tomcat:8.5-jdk8-openjdk」

這樣tomcat容器就啟動了,此時我們通過宿主機的IP位址是訪問不了的,需要對容器埠和宿主機埠進行映射。

停止容器 「docker stop 3854be1d5f93」(容器ID),刪除容器 「docker rm 3854be1d5f93」,然後以如下方式啟動:

「docker run -p 8090:8080 -d tomcat:8.5-jdk8-openjdk」

再訪問就可以了。

快速部署命令:

# 拉取鏡像docker pull 鏡像名<:tags># 查看鏡像列表docker images# 運行容器docker run 鏡像名<:tags># 查看正在運行的容器docker ps# 刪除容器docker rm <-f> 容器id# 刪除鏡像docker rmi <-f> 鏡像名<:tags># 運行容器docker run -p 8000:8080 -d 鏡像名<:tags>

0x04 Docker容器內部結構

一個Docker容器創建好了以後,我們可以進入到容器內部,執行相關的命令查看其內部結構。

以前文創建的Tomcat容器為例:

「查看容器id」

docker ps -a

[root@basic ~]# docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESdd1a6b408cdf hello-world "/hello" 14 hours ago Exited (0) 14 hours ago priceless_kapitsa3854be1d5f93 tomcat:8.5-jdk8-openjdk "catalina.sh run" 25 hours ago Up 24 hours 0.0.0.0:8090->8080/tcp awesome_solomon

得到tomcat容器的id為 「3854be1d5f93」

「進入容器內部」

docker exec [-it] 容器id 命令

  • 「exec」 在對應容器中執行命令
  • 「-it」 採用交互式方式執行命令

因此,進入tomcat容器內部可這樣做:

docker exec -it 3854be1d5f93 /bin/bash

交互式進入Tomcat容器內部,並開啟一個bash終端,

[root@basic ~]# docker exec -it 3854be1d5f93 /bin/bashroot@3854be1d5f93:/usr/local/tomcat#

自動定位到容器內部的/usr/local/tomcat目錄,我們可以在容器內執行一些命令。

「執行腳本命令」

Tomcat容器內置了一個小型的Linux OS,可以執行cat /proc/version看一下其Linux版本:

root@3854be1d5f93:/usr/local/tomcat# cat /proc/versionLinux version 3.10.0-957.el7.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) ) #1 SMP Thu Nov 8 23:39:32 UTC 2018

Tomcat運行依賴於Java環境,再來看一下容器內部Java的版本:

root@3854be1d5f93:/usr/local/tomcat# java -versionopenjdk version "1.8.0_275"OpenJDK Runtime Environment (build 1.8.0_275-b01)OpenJDK 64-Bit Server VM (build 25.275-b01, mixed mode)

0x05 容器生命周期

Docker容器的生命周期中主要有以下幾種狀態:

  • 「stopped」
  • 「running」
  • 「paused」
  • 「deleted」

容器的狀態在我們實際工作中還是挺有用的,容器出問題時,我們首先看一下它的狀態有利於我們定位問題。

容器生命周期的狀態有對應的docker命令。

  • 「docker create」 創建一個新的容器但不啟動它
  • 「docker run」 創建一個新的容器並運行
  • 「docker start/stop/restart」 啟動、停止、重啟容器
  • 「docker kill」 殺掉運行中的容器。「docker stop是優雅地退出」,退出前發送一些信號,docker內部應用做一些退出前的準備工作後再退出;「docker kill是應用程式直接退出」
  • 「docker rm」 刪除容器
  • 「docker pause/unpause」 暫停或恢復容器中的所有進程

0x06 Dockerfile

「Dockerfile」是構建鏡像所用到的最重要的文件。「Dockerfile」是鏡像的描述文件:

  • Dockerfile是一個包含用於「組合鏡像的命令」的文本文檔
  • 「Docker通過讀取Dockerfile中的指令按步驟自動生成鏡像」

構建鏡像標準命令:

docker build -t 構建者/鏡像名<:tags> Dockerfile目錄

比如我們構建一個簡單的基於Tomcat的web鏡像,就一個頁面,我們需要準備一個html頁面和一個Dockerfile鏡像描述文件。

創建一個myweb目錄,裡面有個test.html文件:

<h1>Hello,Dockerfile!<h1>

Dockerfile文件:

FROM xblzer/tomcat:8.5MAINTAINER xblzerWORKDIR /usr/local/tomcat/webappsADD myweb ./myweb

「TIP」:此處myweb和Dockerfile要在同一級目錄。

另外,這個xblzer/tomcat:8.5基礎鏡像是我之前構建好的。也可以使用官方的,不過有可能官方的訪問不了頁面資源(404),這是因為鏡像內部的webapps目錄是空的,但它裡面有webaaps.dist目錄,需要把webaaps.dist裡面的內容拷貝到webapps下才行。

「Dockerfile中的基本命令」

「FROM」

  • 「FROM 鏡像名<:tags>」 基於基準鏡像構建
  • 「FROM scrash」 不依賴於任何基準鏡像

「LABEL & MAINTAINER」

  • 鏡像的說明信息
  • 例:
MAINTAINER xblzerLABEL version=1.0LABEL description="行百里er"

「WORKDIR」

  • 設置工作目錄,指容器內部的工作目錄,需要了解FROM的基礎鏡像內部的結構
  • 儘量使用絕對路徑

比如我們剛才構建的web鏡像,指定工作目錄是/usr/local/tomcat/webapps

「ADD & COPY」

  • ADD和COPY都是複製文件的命令
  • ADD還有遠程複製文件的功能,類似於wget,使用較少

「ENV」

  • 設置環境常量
  • 例如:
ENV JAVA_HOME=/usr/local/javaRUN ${JAVA_HOME}/bin/java -jar test.jar

「根據Dockerfile構建鏡像」

在Dockerfile所在目錄執行:

docker build -t xblzer/myweb:1.0 .

注意,最後面的「 「.」 」。

[root@basic mydockerfile]# docker build -t xblzer/myweb:1.0 .Sending build context to Docker daemon 3.584kBStep 1/4 : FROM xblzer/tomcat:8.5 ---> ad4eef1cdffcStep 2/4 : MAINTAINER xblzer ---> Running in 00ff37cb7a66Removing intermediate container 00ff37cb7a66 ---> 6675a0a2b8beStep 3/4 : WORKDIR /usr/local/tomcat/webapps ---> Running in c753825a9dc3Removing intermediate container c753825a9dc3 ---> cd5999c1d8ffStep 4/4 : ADD myweb ./myweb ---> 9262ba119d14Successfully built 9262ba119d14Successfully tagged xblzer/myweb:1.0

構建完了,用 「docker images」 看一下:

[root@basic mydockerfile]# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZExblzer/myweb 1.0 9262ba119d14 2 minutes ago 537MBxblzer/tomcat 8.5 ad4eef1cdffc 17 minutes ago 537MBtomcat 8.5-jdk8-openjdk 5a5e790eb3eb 4 days ago 533MBhello-world latest bf756fb1ae65 10 months ago 13.3kB

我們構建的鏡像xblzer/test-web:1.0出現在了鏡像列表中。

有了鏡像,按照傳統Docker的運行方式,自然是docker run啟動一個容器:

docker run -d -p 8000:8080 xblzer/myweb:1.0

然後訪問:http://192.168.2.110:8000/myweb/test.html

這說明根據我們構建的鏡像運行的容器沒有問題!

0x07 鏡像分層

再回頭來看一下我之前構建myweb的步驟:

Dockerfile文件:

FROM xblzer/tomcat:8.5MAINTAINER xblzerWORKDIR /usr/local/tomcat/webappsADD myweb ./myweb

構建時的步驟:

Step 1/4 : FROM xblzer/tomcat:8.5 ---> ad4eef1cdffcStep 2/4 : MAINTAINER xblzer ---> Running in 3bfecae6049dRemoving intermediate container 3bfecae6049d ---> 927a6dcf9639Step 3/4 : WORKDIR /usr/local/tomcat/webapps ---> Running in e98ccfd0488cRemoving intermediate container e98ccfd0488c ---> a1dcd9b4885eStep 4/4 : ADD myweb ./myweb ---> 16c0cb847216Successfully built 16c0cb847216Successfully tagged xblzer/myweb:1.0

可以看到,每一步都創建了一個臨時鏡像,這個臨時鏡像有點類似於遊戲存檔,下次如果有用到就直接用這個臨時容器了。

這一個一個的臨時鏡像就是鏡像的分層。

我們來驗證一下。

創建一個Dockerfile:

FROM centosRUN ["echo", "aaa"]RUN ["echo", "bbb"]RUN ["echo", "ccc"]RUN ["echo", "ddd"]

構建:

docker build -t xblzer/test-layer:1.0 .

再將Dockerfile改一下,構建一個1.1版本的鏡像:

FROM centosRUN ["echo", "aaa"]RUN ["echo", "not bbb!!"]RUN ["echo", "not ccc!!!"]RUN ["echo", "ddd"]

構建:

docker build -t xblzer/test-layer:1.1 .

可以看到,第1、2步,使用了之前的臨時鏡像。

我們使用 「docker history」 看一下兩個鏡像的歷史:

版本1.1的前兩個和鏡像1.0版本的前兩步是相同的。

0x08 Docker容器間的通信

Docker在應用部署方面給我們提供了很大的便利,很多情況下,一個應用部署在一個Docker容器中。比如應用程式和資料庫都可以用Docker部署。

那麼在這種情況下,應用程式的Docker容器如何訪問資料庫的Docker容器呢?這就涉及到「容器間的通信」問題。

用docker容器的虛擬ip當然是可以的,查看docker容器ip地址可以使用如下命令:

docker inspect 容器id

但是,線上真實環境一般是不會這麼用的,因為容器有可能會被誤操作而導致容器內部IP位址改變,從而引發連接不通的情況。

Docker的解決方案是給容器起個名字(「docker run --name」),用「容器的名稱」進行容器間的通信。

Docker容器間的通信方式:

  • Link 單向訪問
  • Bridge 網橋雙向訪問

下面我們創建兩個容器,來實驗容器間的通信。

8.1 創建容器

創建一個名稱為web的容器

docker run -d --name web tomcat:8.5-jdk8-openjdk

再創建一個名稱為db的容器

docker run -d -it --name db centos /bin/bash

然後分別使用docker inspect 容器id命令查看兩個容器的虛擬ip。

db容器:172.17.0.4

web容器:172.17.0.3

使用虛擬IP進入web容器ping DB容器:

同樣,db容器內也能ping通web容器的虛擬IP:

8.2 容器間Link單向通信

前文說了,我們一般不使用虛擬IP位址來進行通信,使用容器的name,這就需要在創建容器的時候指定和哪個容器進行 「link」

使用 「--link」 創建web容器,使其連接db容器:

docker run -d --name web --link db tomcat:8.5-jdk8-openjdk

然後進入到該容器內部,直接 「ping db」 就可以連接。

8.3 Bridge網橋雙向通信

使用 「--link」 可以實現容器間的單向通信,

比如我沒有讓db容器link到web容器,在db容器內部ping一下web看看:

有時候我們希望兩個容器能夠互聯互通,怎麼辦呢?

讓兩個容器互相link,這種方法可以,但是容器很多的情況下會很麻煩。

Docker提供了Bridge網橋的方式,讓一組綁定到網橋上的Docker能夠互聯互通。

8.3.1 創建bridge網橋並綁定容器

「1. 創建網橋」

docker network create -d bridge my-bridge

「2. 查看網橋」

docker network ls

「3. 容器與網橋綁定」

將web和db容器都綁定到創建的 「my-bridge」 網橋:

# 先後執行docker network connect my-bridge webdocker network connect my-bridge db

這個時候再進入db容器就能ping通web容器了:

再加一個容器,也綁定到 「my-bridge」 網橋上

docker run -d -it --name myapp centos /bin/bashdocker network connect my-bridge myapp

進入到myapp容器,分別連web和db容器:

均能互聯互通。

8.3.2 網橋實現原理

每當創建了一個網橋,它都會在宿主機上安裝一個「虛擬網卡」,承擔「網關」的作用,由「虛擬網卡構成的網關形成了內部的一個通路」,只要有容器綁定到這個虛擬網卡上,就都能互聯互通。

但是,虛擬網卡終究是虛擬的,IP位址也是虛擬的,如果要和外部通信的話,還必須要「和宿主機的物理網卡進行地址轉換」

容器內部發送的數據包都會經過虛擬網卡做地址轉換,將其轉成物理網卡的數據包向外網進行通信;

同樣,從外網回來的數據先進入物理網卡,之後再通過地址轉換進入到虛擬網卡,再由虛擬網卡進行數據的分發。

0x09 Docker容器間共享數據

9.1 為什麼要進行數據共享

我現在所在的項目做持續部署的時候,每次提交代碼後都用Docker構建鏡像,啟動容器。

試想一下,每次都啟動,那麼日誌啊、圖片啊、文件啊什麼的就都沒了,這是不行的,因此需要指定一個宿主機上的實際存在的目錄和Docker容器內部相應的路徑進行對應。

還有一種場景就是多個容器間都需要訪問一些公共的靜態頁面,這是也可以把公共的頁面放到一個固定的地方,讓容器進行目錄掛載。

9.2 通過設置 「-v」 掛載宿主機目錄

「命令格式」

docker run --name 容器名 -v 宿主機路徑:容器內掛載路徑 鏡像名

還是以tomcat容器為例。

將tomcat容器內部的/usr/local/tomcat/webapps目錄映射到宿主機/usr/webapps目錄,這樣訪問tomcat的頁面時就會訪問宿主機的/usr/webapps下的頁面。

docker run -d -p 8001:8080 --name app1 -v /usr/webapps:/usr/local/tomcat/webapps xblzer/tomcat:8.5

在宿主機/usr/webapps下創建app-web目錄,並新建test.html

[root@basic webapps]# mkdir app-web[root@basic webapps]# cd app-web/[root@basic app-web]# vim test.html

html內容:

<h1>111</h1>

此時訪問:http://192.168.2.110:8001/app-web/test.html

這個時候我們來修改一下/usr/webapps/app-web/test.html

<h1>111</h1><h1>222 added</h1>

不用重啟docker容器,再次訪問:

厲害不?不用重啟Docker容器就能訪問更新過的文件!

9.3 通過 「--volumes-from」 共享容器內掛載點

還有一種掛載目錄的方法就是通過創建共享容器的方式來實現。

「創建共享容器」

docker create --name commonpage -v /usr/webapps:/usr/local/tomcat/webapps xblzer/tomcat:8.5 /bin/true

/bin/true 是占位符,無實際意義。

「共享容器掛載點」

docker run -d -p 8002:8080 --volumes-from commonpage --name app2 xblzer/tomcat:8.5

共享容器的目的是定義好掛載點,然後其他容器通過 --volumes-from 共享容器名來實現和共享容器同樣的掛載目錄。

這樣做的好處是,如果容器非常多,而且掛載目錄有變化時,不用每個容器都去通過-v修改掛載點,只需要修改共享容器的掛載目錄即可。

再來創建一個app3容器:

docker run -d -p 8003:8080 --volumes-from commonpage --name app3 xblzer/tomcat:8.5

此時訪問http://192.168.2.110:8002/app-web/test.htmlhttp://192.168.2.110:8003/app-web/test.html均能訪問到test.html頁面。

0x10 Docker Compose

「Docker Compose」是Docker官方提供的「容器編排」工具,所謂容器編排就是按順序將一個個應用(微服務)在網絡級別進行組織,以使其能夠按照計劃運行。

  • Docker Compose 是單機多容器部署工具,只能在一台主機上工作
  • 通過yml文件定義多容器如何部署
  • Linux下需要安裝Docker Compose

安裝方法:

sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-composesudo chmod +x /usr/local/bin/docker-composedocker-compose --version

然而,眾所周知,「Kubernetes」才是當下最流行的容器編排平台,不管是生產環境的採用率,還是雲原生生態都很強大。

so,這裡不說太多了,給自己挖個坑,留著下次水「K8S」的文章。


首發公眾號 「行百里er」 ,歡迎老鐵們關注閱讀指正。代碼倉庫 「GitHub」 github.com/xblzer/JavaJourney

聲明:文章觀點僅代表作者本人,PTTZH僅提供信息發布平台存儲空間服務。
喔!快樂的時光竟然這麼快就過⋯