Docker實戰——項目容器化改造實戰

What-什麼是容器

容器是一種輕量級、可移植、自包含的軟件打包技術,使應用程序可以在幾乎任何地方以相同的方式運行。開發人員在自己筆記本上創建並測試好的容器,無需任何修改就能夠在生產系統的虛擬機、物理服務器或公有云主機上運行。

容器與虛擬機

談到容器,就不得不將它與虛擬機進行對比,因爲兩者都是爲應用提供封裝和隔離。

容器由兩部分組成:

  1. 應用程序本身
  2. 依賴:比如應用程序需要的庫或其他軟件

容器在 Host 操作系統的用戶空間中運行,與操作系統的其他進程隔離。這一點顯著區別於的虛擬機。

傳統的虛擬化技術,比如 VMWare, KVM, Xen,目標是創建完整的虛擬機。爲了運行應用,除了部署應用本身及其依賴(通常幾十 MB),還得安裝整個操作系統(幾十 GB)。

下圖展示了二者的區別。

如圖所示,由於所有的容器共享同一個 Host OS,這使得容器在體積上要比虛擬機小很多。另外,啓動容器不需要啓動整個操作系統,所以容器部署和啓動速度更快,開銷更小,也更容易遷移。

Why - 爲什麼需要容器?

爲什麼需要容器?容器到底解決的是什麼問題?
簡要的答案是:容器使軟件具備了超強的可移植能力

容器解決的問題

我們來看看今天的軟件開發面臨着怎樣的挑戰?

如今的系統在架構上較十年前已經變得非常複雜了。以前幾乎所有的應用都採用三層架構(Presentation/Application/Data),系統部署到有限的幾臺物理服務器上(Web Server/Application Server/Database Server)。

而今天,開發人員通常使用多種服務(比如 MQCacheDB)構建和組裝應用,而且應用很可能會部署到不同的環境,比如虛擬服務器,私有云和公有云。

一方面應用包含多種服務,這些服務有自己所依賴的庫和軟件包;另一方面存在多種部署環境,服務在運行時可能需要動態遷移到不同的環境中。這就產生了一個問題:

如何讓每種服務能夠在所有的部署環境中順利運行?

於是我們得到了下面這個矩陣:

各種服務和環境通過排列組合產生了一個大矩陣。開發人員在編寫代碼時需要考慮不同的運行環境,運維人員則需要爲不同的服務和平臺配置環境。對他們雙方來說,這都是一項困難而艱鉅的任務。

如何解決這個問題呢?

聰明的技術人員從傳統的運輸行業找到了答案。

幾十年前,運輸業面臨着類似的問題。

每一次運輸,貨主與承運方都會擔心因貨物類型的不同而導致損失,比如幾個鐵桶錯誤地壓在了一堆香蕉上。另一方面,運輸過程中需要使用不同的交通工具也讓整個過程痛苦不堪:貨物先裝上車運到碼頭,卸貨,然後裝上船,到岸後又卸下船,再裝上火車,到達目的地,最後卸貨。一半以上的時間花費在裝、卸貨上,而且搬上搬下還容易損壞貨物。

這同樣也是一個 NxM 的矩陣。

幸運的是,集裝箱的發明解決這個難題。

任何貨物,無論鋼琴還是保時捷,都被放到各自的集裝箱中。集裝箱在整個運輸過程中都是密封的,只有到達最終目的地才被打開。標準集裝箱可以被高效地裝卸、重疊和長途運輸。現代化的起重機可以自動在卡車、輪船和火車之間移動集裝箱。集裝箱被譽爲運輸業與世界貿易最重要的發明。

Docker 將集裝箱思想運用到軟件打包上,爲代碼提供了一個基於容器的標準化運輸系統。Docker 可以將任何應用及其依賴打包成一個輕量級、可移植、自包含的容器。容器可以運行在幾乎所有的操作系統上。

其實,集裝箱容器對應的英文單詞都是 “Container”
容器是國內約定俗成的叫法,可能是因爲容器比集裝箱更抽象,更適合軟件領域的原故吧。

我個人認爲:在老外的思維中,“Container” 只用到了集裝箱這一個意思,Docker Logo 不就是一堆集裝箱嗎?

Docker 的特性

我們可以看看集裝箱思想是如何與 Docker 各種特性相對應的。

特性

集裝箱

Docker

打包對象

幾乎任何貨物

任何軟件及其依賴

硬件依賴

標準形狀和接口允許集裝箱被裝卸到各種交通工具,整個運輸過程無需打開

容器無需修改便可運行在幾乎所有的平臺上 -- 虛擬機、物理機、公有云、私有云

隔離性

集裝箱可以重疊起來一起運輸,香蕉再也不會被鐵桶壓爛了

資源、網絡、庫都是隔離的,不會出現依賴問題

自動化

標準接口使集裝箱很容易自動裝卸和移動

提供 run, start, stop 等標準化操作,非常適合自動化

高效性

無需開箱,可在各種交通工具間快速搬運

輕量級,能夠快速啓動和遷移

職責分工

貨主只需考慮把什麼放到集裝箱裏;承運方只需關心怎樣運輸集裝箱

開發人員只需考慮怎麼寫代碼;運維人員只需關心如何配置基礎環境

容器的優勢

對於開發人員Build Once, Run Anywhere

容器意味着環境隔離和可重複性。開發人員只需爲應用創建一次運行環境,然後打包成容器便可在其他機器上運行。另外,容器環境與所在的 Host 環境是隔離的,就像虛擬機一樣,但更快更簡單。

對於運維人員Configure Once, Run Anything

只需要配置好標準的 runtime 環境,服務器就可以運行任何容器。這使得運維人員的工作變得更高效,一致和可重複。容器消除了開發、測試、生產環境的不一致性。

How - 容器是如何工作的?

從下節開始我們將學習容器核心知識的最主要部分。首先會介紹 Docker 的架構,然後分章節詳細討論 Docker 的鏡像、容器、網絡和存儲。

Docker架構詳解

Docker 的核心組件包括:

  1. Docker 客戶端 - Client
  2. Docker 服務器 - Docker daemon
  3. Docker 鏡像 - Image
  4. Registry
  5. Docker 容器 - Container

Docker 架構如下圖所示:

Docker 採用的是 Client/Server 架構。客戶端向服務器發送請求,服務器負責構建、運行和分發容器。客戶端和服務器可以運行在同一個 Host 上,客戶端也可以通過 socket REST API 與遠程的服務器通信。

Docker 客戶端

最常用的 Docker 客戶端是 docker 命令。通過 docker 我們可以方便地在 Host 上構建和運行容器。

docker 支持很多操作(子命令),後面會逐步用到。

除了 docker 命令行工具,用戶也可以通過 REST API 與服務器通信。

Docker 服務器

Docker daemon 是服務器組件,以 Linux 後臺服務的方式運行。

Docker daemon 運行在 Docker host 上,負責創建、運行、監控容器,構建、存儲鏡像。

默認配置下,Docker daemon 只能響應來自本地 Host 的客戶端請求。如果要允許遠程客戶端請求,需要在配置文件中打開 TCP 監聽,步驟如下:

  1. 編輯配置文件 /etc/systemd/system/multi-user.target.wants/docker.service,在環境變量 ExecStart 後面添加 -H tcp://0.0.0.0,允許來自任意 IP 的客戶端連接。



    如果使用的是其他操作系統,配置文件的位置可能會不一樣。
  2. 重啓 Docker daemon
     

  3. 服務器 IP 192.168.56.102,客戶端在命令行里加上 -H 參數,即可與遠程服務器通信。
     



    info 子命令用於查看 Docker 服務器的信息。

Docker 鏡像

可將 Docker 鏡像看着只讀模板,通過它可以創建 Docker 容器。

例如某個鏡像可能包含一個 Ubuntu 操作系統、一個 Apache HTTP Server 以及用戶開發的 Web 應用。

鏡像有多種生成方法:

  1. 可以從無到有開始創建鏡像
  2. 也可以下載並使用別人創建好的現成的鏡像
  3. 還可以在現有鏡像上創建新的鏡像

我們可以將鏡像的內容和創建步驟描述在一個文本文件中,這個文件被稱作 Dockerfile,通過執行 docker build <docker-file> 命令可以構建出 Docker 鏡像,後面我們會討論。

Docker 容器

Docker 容器就是 Docker 鏡像的運行實例。

用戶可以通過 CLIdocker)或是 API 啓動、停止、移動或刪除容器。可以這麼認爲,對於應用軟件,鏡像是軟件生命週期的構建和打包階段,而容器則是啓動和運行階段。

Registry

Registry 是存放 Docker 鏡像的倉庫,Registry 分私有和公有兩種。

Docker Hubhttps://hub.docker.com/ 是默認的 Registry,由 Docker 公司維護,上面有數以萬計的鏡像,用戶可以自由下載和使用。

出於對速度或安全的考慮,用戶也可以創建自己的私有 Registry。後面我們會學習如何搭建私有 Registry

docker pull 命令可以從 Registry 下載鏡像。
docker run 命令則是先下載鏡像(如果本地沒有),然後再啓動容器。

docker私有鏡像中心搭建

拉取registry2版本的鏡像並啓動鏡像容器,-v設置映射地址(本機:容器),映射端口號

docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 registry:2

修改需要上傳至私有鏡像中心的 鏡像名稱

docker tag task-web-server:2.20.1 192.168.5.206:5000/dmp/task-web-server:2.20.1

紅色:爲鏡像中心的insecure-registries的值

{
        "registry-mirrors": ["https://njrds9qc.mirror.aliyuncs.com"],
        "insecure-registries":["192.168.5.206:5000"]
}

修改完後需要重啓docker

systemctl daemon-reload
systemctl restart docker

查看鏡像中心的鏡像

http://192.168.5.206:5000/v2/_catalog

或者用curl命令

curl -I -X DELETE http://192.168.5.206:5000/v2/_catalog/dmp/task-web-server

向鏡像中心推送鏡像

docker push 192.168.5.206:5000/dmp/task-web-server:2.20.1

鏡像中心刪除鏡像

curl -I -X DELETE http://192.168.5.206:5000/v2/192.168.5.206:5000/dmp/streaming-web-server/manifests/sha256:011acab4bc9654776b05a26590ab23451bf1fec4fb859b74e3e995f7eb55f3d8

Dockerfile

Dockerfile命令詳解

Dockerfile 文件常用命令說明

FROM

指定base鏡像

MAINTAINER

設置鏡像的作者,可以是任意字符串

COPY

將文件從build context複製到鏡像

COPY支持兩種形式:

  1. COPY src dest
  2. COPY ["src","dest"]

注意:src只能指定build context中的文件或目錄

ADD

與COPY類似,從build context複製文件到鏡像。不同的是如果src是歸檔文件(tar,zip,tgz,xz等),文件會被自動解壓到dest。

ENV

設置環境變量,環境變量可被後面的指令使用。例如:

ENV MY_VERSION 1.3

RUN apt-get install -y mypackage=$MY_VERSION

EXPOSE

指定容器中的進程會監聽某個端口,Docker可以將該端口暴露出來。我們會咋容器網絡部分詳細討論。

VOLUME

將文件或目錄生命爲volume。

WORKDIR

爲後面的RUN,CMD,ENTRYPOINT,ADD或COPY指定設置鏡像中的當前工作目錄

RUN

在容器中運行指定的命令

CMD

容器啓動時運行指定的命令。

Dockerfile中可以有多個CMD指令,但只有最後一個生效。CMD可以被docker run之後的參數替換。

ENTRYPOINT

設置容器啓動時運行的命令。

Dockerfile中可以有多個ENTRYPOINT指令,但只有最後一個生效。CMD或docker run之後的參數會被當做參數傳遞給ENTRYPOINT

上爲一個簡單的Dockerfile   創建了一個tmpfile1文件,並將tmpfile2複製進的鏡像當前目錄/testdir,並將bunch.tar.gz解壓複製進了/testdir目錄。

構建鏡像

①構建前確保build context中存在需要的文件。

②依次執行Dockerfile指令,完成構建。

運行容器,驗證鏡像內容:

①進入容器,當前目錄爲WORKDIR,如果不存在,Docker會自動爲我們創建。

②WORKDIR中保存了我們希望存在的目錄

③ENV指令定義的環境變量已經生效。

通過Dockerfile構建鏡像並啓動容器

#構建task-web-server

docker build -t 192.168.5.206:5000/dmp/task-web-server:2.20.2 .

#容器啓動命令

docker run -it -p 8077:8022 192.168.5.206:5000/dmp/task-web-server:2.20.2

docker自定義網絡配置

docker network create --driver bridge my_net

brctl show

docker network inspect my_net

創建網橋時加入 網段subnet 網關gateway

docker network create --driver bridge --subnet 172.22.16.0/24 --gateway 172.22.16.1 my_net

容器啓動時,通過--network指定網橋

docker run -it --network=my_net task-web-server:1.0.0

可以手動指定網段分配ip

docker run -it --network=my_net --ip 172.22.16.8 task-web-server:1.0.0

項目Docker架構猜想

Docker的CI猜想

引入Docker-Compose容器管理工具

Compose是一個用於定義和運行多容器Docker應用程序的工具。使用Compose,您可以使用YAML文件來配置應用程序的服務。然後,使用單個命令,您可以從配置中創建並啓動所有服務。

使用docker Compose基本上是三步驟:

  1. 定義應用程序的Dockerfile文件
  2. 定義構成應用程序的服務,docker-compose.yml以便於可以在隔離環境中一起運行
  3. run docker-compose up和compose啓動並運行整個應用程序。

Compose具有管理應用程序整個生命週期的命令:

  • 啓動、停止和重建服務
  • 查看正在運行的服務的狀態
  • 流式傳輸運行服務的日誌輸出
  • 在服務上運行一次行命令

在Linux系統中Compose的部署

安裝Compose

  • 運行下命令以下載Docker Compose的當前穩定版本:

我對官網方法呵呵了,curl連接有問題。這裏提供無坑方法

官網找到最新穩定版本:https://github.com/docker/compose/releases/ , 下載對應二進制文件,ftp到服務器的/usr/local/bin/目錄下,並將文件名改爲docker-compose。

  • 對二進制文件應用賦予root權限:

sudo chmod +x /usr/local/bin/docker-compose

卸載

卸載docker Compose:

sudo rm /usr/local/bin/docker-compose

YAML 配置命令

配置

說明

build        

指定 Dockerfile 所在的目錄地址,用於構建鏡像,並使用此鏡像創建容器,比如上面配置的build: .

command

容器的執行命令

dns

自定義 dns 服務器

expose

暴露端口配置,但不映射到宿主機,只被連接的服務訪問

extends

對docker-compose.yml的擴展,配置在服務中

image

使用的鏡像名稱或鏡像 ID

links

鏈接到其它服務中的容器(一般橋接網絡模式使用)

net

設置容器的網絡模式(四種:bridge, none, container:[name or id]和host)

ports

暴露端口信息,主機和容器的端口映射

volumes

數據卷所掛載路徑設置

Docker Compose 常用命令

命令

說明

docker-compose build

構建項目中的鏡像,--force-rm:刪除構建過程中的臨時容器;--no-cache:不使用緩存構建;--pull:獲取最新版本的鏡像

docker-compose up -d

構建鏡像、創建服務和啓動項目,-d表示後臺運行

docker-compose run ubuntu ls -d

指定服務上運行一個命令,-d表示後臺運行

docker-compose logs

查看服務容器輸出日誌

docker-compose ps

列出項目中所有的容器

docker-compose pause [service_name]

暫停一個服務容器

docker-compose unpause [service_name]

恢復已暫停的一個服務容器

docker-compose restart

重啓項目中的所有服務容器(也可以指定具體的服務)

docker-compose stop

停止運行項目中的所有服務容器(也可以指定具體的服務)

docker-compose start

啓動已經停止項目中的所有服務容器(也可以指定具體的服務)

docker-compose rm

刪除項目中的所有服務容器(也可以指定具體的服務),-f:強制刪除(包含運行的)

docker-compose kill

強制停止項目中的所有服務容器(也可以指定具體的服務)

Docker-Compose工作原理

docker-compose.yml示例

#通過構建當前目錄的DockerFile文件進行鏡像的構建,並啓動容器。

version: "3.7"

services:

  dmp-web-server:

    build: .

    ports:

      - "8077:8022"

      - "8078:8023"

Jenkins+Docker

<?xml version='1.1' encoding='UTF-8'?>
<maven2-moduleset plugin="[email protected]">
    <actions/>
    <description></description>
    <keepDependencies>false</keepDependencies>
    <properties/>
    <scm class="hudson.scm.SubversionSCM" plugin="[email protected]">
        <locations>
            <hudson.scm.SubversionSCM_-ModuleLocation>
                <remote>https://192.168.5.124/svn/1_developmentDW/ZBMP/taskmanagement/2_src/1_trunk/task-manage</remote>
                <credentialsId>ed46a3cd-78bb-4a2f-858d-77cbe79a6099</credentialsId>
                <local>.</local>
                <depthOption>infinity</depthOption>
                <ignoreExternalsOption>true</ignoreExternalsOption>
                <cancelProcessOnExternalsFail>true</cancelProcessOnExternalsFail>
            </hudson.scm.SubversionSCM_-ModuleLocation>
        </locations>
        <excludedRegions></excludedRegions>
        <includedRegions></includedRegions>
        <excludedUsers></excludedUsers>
        <excludedRevprop></excludedRevprop>
        <excludedCommitMessages></excludedCommitMessages>
        <workspaceUpdater class="hudson.scm.subversion.UpdateUpdater"/>
        <ignoreDirPropChanges>false</ignoreDirPropChanges>
        <filterChangelog>false</filterChangelog>
        <quietOperation>true</quietOperation>
    </scm>
    <canRoam>true</canRoam>
    <disabled>false</disabled>
    <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
    <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
    <triggers>
        <hudson.triggers.TimerTrigger>
            <spec>H 23 * * *</spec>
        </hudson.triggers.TimerTrigger>
    </triggers>
    <concurrentBuild>false</concurrentBuild>
    <rootModule>
        <groupId>com.aotain</groupId>
        <artifactId>task-manage</artifactId>
    </rootModule>
    <goals>clean install -Dmaven.test.skip=true</goals>
    <aggregatorStyleBuild>true</aggregatorStyleBuild>
    <incrementalBuild>false</incrementalBuild>
    <ignoreUpstremChanges>true</ignoreUpstremChanges>
    <ignoreUnsuccessfulUpstreams>false</ignoreUnsuccessfulUpstreams>
    <archivingDisabled>false</archivingDisabled>
    <siteArchivingDisabled>false</siteArchivingDisabled>
    <fingerprintingDisabled>false</fingerprintingDisabled>
    <resolveDependencies>false</resolveDependencies>
    <processPlugins>false</processPlugins>
    <mavenValidationLevel>-1</mavenValidationLevel>
    <runHeadless>false</runHeadless>
    <disableTriggerDownstreamProjects>false</disableTriggerDownstreamProjects>
    <blockTriggerWhenBuilding>true</blockTriggerWhenBuilding>
    <settings class="jenkins.mvn.DefaultSettingsProvider"/>
    <globalSettings class="jenkins.mvn.DefaultGlobalSettingsProvider"/>
    <reporters/>
    <publishers>
        <jenkins.plugins.publish__over__ssh.BapSshPublisherPlugin plugin="[email protected]">
            <consolePrefix>SSH:</consolePrefix>
            <delegate plugin="[email protected]">
                <publishers>
                    <jenkins.plugins.publish__over__ssh.BapSshPublisher plugin="[email protected]">
                        <configName>192.168.5.206-docker_registry</configName>
                        <verbose>false</verbose>
                        <transfers>
                            <jenkins.plugins.publish__over__ssh.BapSshTransfer>
                                <remoteDirectory>/opt/task-manage</remoteDirectory>
                                <sourceFiles>result/*.*</sourceFiles>
                                <excludes></excludes>
                                <removePrefix>result</removePrefix>
                                <remoteDirectorySDF>false</remoteDirectorySDF>
                                <flatten>false</flatten>
                                <cleanRemote>false</cleanRemote>
                                <noDefaultExcludes>false</noDefaultExcludes>
                                <makeEmptyDirs>false</makeEmptyDirs>
                                <patternSeparator>[, ]+</patternSeparator>
                                <execCommand>pwd
                                    cd /opt/task-manage
                                    chmod +x task-spark-with-dependencies.jar
                                    \cp task-spark-with-dependencies.jar task-spark/mr/
                                    rm -f task-spark.tar.gz
                                    tar zcvf task-spark.tar.gz task-spark
                                    chmod +x *.tar.gz
                                    sudo docker-compose build
                                    sudo docker-compose up -d
                                </execCommand>
                                <execTimeout>120000</execTimeout>
                                <usePty>false</usePty>
                                <useAgentForwarding>false</useAgentForwarding>
                            </jenkins.plugins.publish__over__ssh.BapSshTransfer>
                        </transfers>
                        <useWorkspaceInPromotion>false</useWorkspaceInPromotion>
                        <usePromotionTimestamp>false</usePromotionTimestamp>
                    </jenkins.plugins.publish__over__ssh.BapSshPublisher>
                </publishers>
                <continueOnError>false</continueOnError>
                <failOnError>false</failOnError>
                <alwaysPublishFromMaster>false</alwaysPublishFromMaster>
                <hostConfigurationAccess class="jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin"
                                         reference="../.."/>
            </delegate>
        </jenkins.plugins.publish__over__ssh.BapSshPublisherPlugin>
    </publishers>
    <buildWrappers/>
    <prebuilders/>
    <postbuilders>
        <hudson.tasks.Shell>
            <command>pwd
                rm -rf result
                mkdir result

                \cp monitor-persist/target/*.tar.gz result/monitor-persist.tar.gz
                \cp data-collection/target/*.tar.gz result/data-collection.tar.gz
                \cp streaming-web-server/target/*.tar.gz result/streaming-web-server.tar.gz
                \cp task-web-server/target/*.tar.gz result/task-web-server.tar.gz

                #單獨處理web-resource
                tar zcvf result/web-resource.tar.gz web-resource

                #單獨處理task-spark
                \cp task-spark/target/*-with-dependencies.jar result/task-spark-with-dependencies.jar
            </command>
        </hudson.tasks.Shell>
    </postbuilders>
    <runPostStepsIfResult>
        <name>FAILURE</name>
        <ordinal>2</ordinal>
        <color>RED</color>
        <completeBuild>true</completeBuild>
    </runPostStepsIfResult>
</maven2-moduleset>

Jenkins中的腳本命令參考config.xml文件即可。

參考文檔:

《Docker Documentation》

《使用kubernetes部署高可用Docker私有鏡像倉庫》

《每天5分鐘玩轉容器技術》

《用Docker實現私有云》

用Docker封裝一個web應用(Django)

《Docker安裝Tomcat》

《從零開始搭建docker私有倉庫》

《從零開始使用Docker搭建Spark集羣》

《Dockerfile命令詳解》

《通過Dockerfile構建SpringBoot項目鏡像》

《Spring Boot with Docker》

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章