云原生系列Kubernetes篇 創(chuàng)建、運(yùn)行容器

Kubernetes是用于創(chuàng)建、部署和管理分發(fā)應(yīng)用的平臺(tái)。這些應(yīng)用大小、形態(tài)各異,但最終都由在具體機(jī)器上運(yùn)行的一個(gè)或多個(gè)程序組成。這些應(yīng)用會(huì)接收輸入、操作數(shù)據(jù)、返回結(jié)果。在構(gòu)建分布式系統(tǒng)之前,我們要先考慮構(gòu)建包含這些應(yīng)用程序的容器鏡像以及組成我們分布式系統(tǒng)的零件。

應(yīng)用程序通常由編程語言運(yùn)行時(shí)、庫文件和源代碼組成。大部分場(chǎng)景下我們的應(yīng)用會(huì)依賴一些外部共享庫,如libc和libssl。這些外部庫通常是主機(jī)或服務(wù)器上所安裝操作系統(tǒng)的共享組件。

這些共享可能會(huì)引發(fā)一些問題,比如程序員開發(fā)應(yīng)用的筆記本電腦上存在的共享庫依賴在運(yùn)行程序的生產(chǎn)環(huán)境系統(tǒng)可能并不存在。即使開發(fā)和生成環(huán)境使用了相同操作系統(tǒng)的相同版本,還是有存在問題的可能性,開發(fā)者可能會(huì)忘記在部署到生產(chǎn)環(huán)境時(shí)忘記在包內(nèi)添加依賴的資源文件。

傳統(tǒng)單機(jī)運(yùn)行多程序的方法是所有程序均依賴相同版本的系統(tǒng)共享庫。如果不同團(tuán)隊(duì)或組織分別開發(fā)了程序,這些共享依賴會(huì)帶來不必要的復(fù)雜性以及跨團(tuán)隊(duì)的耦合性。

這導(dǎo)致程序要部署至指定的機(jī)器上才能成功執(zhí)行。經(jīng)常主流部署需要添加命令式腳本,這又引入了隱藏問題及拜占庭錯(cuò)誤。因而分布式系統(tǒng)各部分新版本的發(fā)布不僅大費(fèi)周章而且困難重重。

不可變鏡像及基礎(chǔ)設(shè)施的巨大價(jià)值已經(jīng)過論證,容器鏡像所提供的正是這種不可變性。可以看到,它輕易地解決了剛剛提到的所有依賴管理和封裝問題。

應(yīng)用程序打包后分享的便捷性也很重要?,F(xiàn)在大家打包執(zhí)行文件的默認(rèn)使用Docker,可將鏡像推送到倉(cāng)庫,之后再供其他人拉取?,F(xiàn)在各大公有云都有自己的容器倉(cāng)庫,很多也提供了構(gòu)建鏡像的服務(wù)。我們也可以使用開源或商業(yè)系統(tǒng)運(yùn)行自己的鏡像倉(cāng)庫。通過倉(cāng)庫可以便捷地管理及部署私有鏡像,而鏡像構(gòu)建服務(wù)又可輕松地集成持續(xù)部署系統(tǒng)。

下面我們通過一個(gè)簡(jiǎn)單應(yīng)用來演示這一工作流。

容器鏡像將應(yīng)用程序及其依賴打包為位于同一個(gè)文件系統(tǒng)中的鏡像。最通行的窗口鏡像格式為Docker鏡像,其已由開放容器計(jì)劃標(biāo)準(zhǔn)化為OCI的鏡像格式。Kubernetes同時(shí)支持Docker鏡像及借由Docker和其它運(yùn)行時(shí)打包的兼容OCI的鏡像。Docker鏡像還包含容器運(yùn)行時(shí)用于按鏡像內(nèi)容啟動(dòng)應(yīng)用實(shí)例的元數(shù)據(jù)。

接下來我們講解:

  • 如何使用Docker鏡像格式打包應(yīng)用
  • 如何使用Docker容器運(yùn)行時(shí)啟動(dòng)應(yīng)用

容器鏡像

大部分人初次都是通過容器鏡像接觸的容器技術(shù)。容器鏡像是一個(gè)二進(jìn)行包,其中包含操作系統(tǒng)容器運(yùn)行程序所需要的所有文件??梢宰约涸诒镜貥?gòu)建容器鏡像或是從鏡像倉(cāng)庫下載已有的鏡像。不管是哪一種,都可以在電腦上運(yùn)行該鏡像來在容器中運(yùn)行所含的應(yīng)用。

Docker鏡像格式

最通行的容器鏡像格式是Docker鏡像格式,由Docker開源項(xiàng)目開發(fā),用于使用docker命令打包、分發(fā)及運(yùn)行容器。后來Docker, Inc.及其它方通過OCI標(biāo)準(zhǔn)化容器鏡像格式。雖然OCI于2017年中發(fā)布了1.0里程碑版本,該標(biāo)準(zhǔn)的實(shí)施進(jìn)程緩慢。Docker鏡像格式繼續(xù)扮演事實(shí)標(biāo)準(zhǔn)的角色,它由一系列文件系統(tǒng)分層組成。每層添加、刪除或修改之前分層中的文件。這是一種overlay文件系統(tǒng)。overlay系統(tǒng)用于鏡像的打包及使用。在運(yùn)行時(shí),有很多這類文件系統(tǒng)的具體實(shí)現(xiàn),包括aufs、overlay和andoverlay2。

容器分層

Docker鏡像格式容器鏡像聽起來有些唬人。鏡像并不是某個(gè)文件,而是指向其它文件的聲明文件的描述。聲明文件及其關(guān)聯(lián)文件通常被視作一個(gè)單元。這種間接層級(jí)可實(shí)現(xiàn)更高效的存儲(chǔ)和傳輸。與這一格式相關(guān)聯(lián)的是將鏡像上傳到倉(cāng)庫或下載的API。

容器鏡像由一系列文件系統(tǒng)分層組成,其中各層繼承、修改此前的分層。為便于詳細(xì)講解,我們來構(gòu)建一些容器。注意正確的分層是自下向上的,但為了方便理解,我們進(jìn)行了反向展示:

.
└── container A: 一個(gè)基礎(chǔ)操作系統(tǒng),比如Debian
    └── container B: 在A的基礎(chǔ)上構(gòu)建,添加了Ruby v2.1.10
    └── container C: 在A的基礎(chǔ)上構(gòu)建,添加了Golang v1.6

此時(shí)我們有了三個(gè)容器:A、B和C。B和C又通過A構(gòu)建,它們除了基礎(chǔ)鏡像文件以外并無共同之處。更進(jìn)一步,我們可以基于B進(jìn)行構(gòu)建,添加Rails 4.2.6。可能還會(huì)希望支持使用更老版本Rails(如3.2.x)應(yīng)用。可以基于B構(gòu)建一個(gè)容器鏡像來兼容該應(yīng)用,在未來再將應(yīng)用遷移至版本4。

.(接上)
└── container B: 在A的基礎(chǔ)上構(gòu)建,添加了Ruby v2.1.10
    └── container D: 在B的基礎(chǔ)上構(gòu)建,添加了Rails v4.2.6
    └── container E: 在B的基礎(chǔ)上構(gòu)建,添加了Rails v3.2.x

每個(gè)容器鏡像層都構(gòu)建在前一個(gè)之上。每個(gè)父引用都是一個(gè)指針。雖然上例中的容器很簡(jiǎn)單,但真實(shí)的容器可能是一個(gè)巨大的有向無環(huán)圖。

容器鏡像通常由容器配置文件組成,配置文件中提供了如何設(shè)置容器環(huán)境及執(zhí)行應(yīng)用入口的指令。容器配置通常包含的信息有網(wǎng)絡(luò)設(shè)置、命名空間隔離、資源約束(cgroups)以及運(yùn)行容器實(shí)例時(shí)添加的syscall限制。容器根文件系統(tǒng)及配置文件一般使用Docker鏡像格式進(jìn)行綁定。

容器有兩大類:

  • 系統(tǒng)容器
  • 應(yīng)用容器

系統(tǒng)容器模擬虛擬機(jī),通常運(yùn)行完整的啟動(dòng)進(jìn)程。通常包含一組虛擬機(jī)所包含的系統(tǒng)服務(wù),如ssh、cron和syslog。Docker剛出來的時(shí)候,這種類型的容器很普遍。隨著容器的不斷發(fā)展,這被看成是一種不良實(shí)踐,應(yīng)用容器便越來越受青睞。

與系統(tǒng)容器不同,應(yīng)用容器通常運(yùn)行單個(gè)程序。雖然每個(gè)容器運(yùn)行單一程序看起來是沒必要的約束,但它提供了很好的粒度級(jí)別來構(gòu)建可擴(kuò)展應(yīng)用,也是Pod重度采納的設(shè)計(jì)哲學(xué)。有關(guān)Pod的運(yùn)行原理在后續(xù)文章中會(huì)進(jìn)行講解。

使用Docker構(gòu)建應(yīng)用

通常Kubernetes這樣的容器編排系統(tǒng)聚焦于構(gòu)建和部署由應(yīng)用容器構(gòu)成的分布式系統(tǒng)。因此本文接下來會(huì)集中講解應(yīng)用容器。

Dockerfile

Dockerfile可用于自動(dòng)創(chuàng)建Dockerfile容器鏡像。

下面我們使用簡(jiǎn)單的Node.js程序構(gòu)建一個(gè)應(yīng)用鏡像。本例與Python或Ruby等動(dòng)態(tài)編程語言類似。

最簡(jiǎn)單的npm/Node/Express應(yīng)用包含兩個(gè)文件:package.json(例1-1)和server.js(例1-2)。將這兩個(gè)文件放到同一目錄中,運(yùn)行npm install express --save來建立Express依賴并進(jìn)行安裝。

例1-1 package.json

{
  "name": "simple-node",
  "version": "1.0.0",
  "description": "示例應(yīng)用",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "author": ""
}

例1-2 server.js

var express = require('express');

var app = express();
app.get('/', function (req, res) {
  res.send('Hello World!');
})
app.listen(3000, function () {
  console.log('Listening on port 3000!');
  console.log(' http://localhost:3000');
})

要將其打包為Docker鏡像,我們需要再創(chuàng)建兩個(gè)文件:.dockerignore(例1-3)和Dockerfile(例1-4)。Dockerfile是用于構(gòu)建容器鏡像的配置文件,而.dockerignore定義了一組不拷貝到鏡像中的文件。有關(guān)Dockerfile語法的完整描述參見Docker官方網(wǎng)站
例1-3 .dockerignore

node_modules

例1-4 Dockerfile

# 使用Node.js 16 (LTS)鏡像進(jìn)行啟動(dòng) ?
FROM node:16

# 指定鏡像內(nèi)所有命令運(yùn)行的目錄 ②
WORKDIR /usr/src/app

# 復(fù)制包文件并安裝依賴 ③
COPY package*.json ./
RUN npm install
RUN npm install express

# 將所有的應(yīng)用文件拷貝到鏡像中 ④
COPY . .

# 啟動(dòng)容器時(shí)默認(rèn)運(yùn)行的命令 ⑤
CMD [ "npm", "start" ]

? 每個(gè)Dockerfile都構(gòu)建于其它容器鏡像之上。該行指定了Docker Hub中的node:16進(jìn)行啟動(dòng)。這是Node.js 16的預(yù)置鏡像。讀者可能會(huì)問如果我希望從白板開始構(gòu)建呢?那樣的話可以使用FROM scratch

② 該行設(shè)置了容器鏡像中運(yùn)行下面命令中的工作目錄。

③ 這幾行初始化了Node.js的依賴。首先包文件復(fù)制到鏡像中。包含package.jsonpackage-lock.json。接著RUN命令運(yùn)行相應(yīng)的命令安裝所需依賴。

④ 接下來我們將剩下的程序文件拷貝到鏡像中。包含node_modules外的所有文件,因?yàn)槲覀冊(cè)?code>.dockerignore文件中進(jìn)行了排除。

⑤ 最后,我們指定容器啟動(dòng)時(shí)運(yùn)行的命令。

運(yùn)行如下命令創(chuàng)建simple-nodeDocker鏡像:

$ docker build -t simple-node .

如果希望運(yùn)行該鏡像,可以執(zhí)行如下的命令:

$ docker run --rm -p 3000:3000 simple-node

瀏覽http://localhost:3000來訪問容器中運(yùn)行的程序。

此時(shí)我們的simple-node鏡像位于本地Docker倉(cāng)庫,僅能在當(dāng)前機(jī)器訪問。Docker的強(qiáng)大之處在于可以在幾千臺(tái)機(jī)器及更廣大的Docker社區(qū)中共享鏡像。

優(yōu)化鏡像大小

剛開始使用容器鏡像時(shí)常常會(huì)產(chǎn)生過大的鏡像。首先要記住的是后續(xù)層中刪除的文件實(shí)際上仍存在于鏡像中,只是不可訪問。思考如下場(chǎng)景:

.
└── layer A: 包含名為BigFile的大文件
    └── layer B: 刪除BigFile
    └── layer C: 在B基礎(chǔ)上進(jìn)行構(gòu)建,添加靜態(tài)二進(jìn)制文件

讀者可能覺得鏡像中已不再有BigFile了。畢竟在運(yùn)行鏡像時(shí)并不能訪問該文件。但事實(shí)是它仍存在于layer A中,也就是說在推送或拉取鏡像時(shí),BigFile仍通過網(wǎng)絡(luò)進(jìn)行傳輸,雖然無法訪問。

另一個(gè)容易踩的坑是陷入了緩存和構(gòu)建的循環(huán)。記住每層都是對(duì)下面一層的獨(dú)立更改。每次修改某一層時(shí),都會(huì)更改藏兵的每一層。修改前面的層意味著需要重建、重新推送及重新拉取來部署鏡像至開發(fā)環(huán)境。

要進(jìn)行更全面的理解,思考如下兩個(gè)鏡像:

.
└── layer A: 包含基礎(chǔ)OS
    └── layer B: 添加源代碼server.js
    └── layer C: 安裝node包

對(duì)比

.
└── layer A: 包含基礎(chǔ)OS
    └── layer B: 安裝node包
    └── layer C: 添加源代碼server.js

很明顯這兩個(gè)鏡像的行為一致,初次拉取后也正是如此。但考慮下server.js更改后會(huì)發(fā)生什么。一種情況是只修改需要拉取或推送的內(nèi)容,但另一種情況是server.js和提供node包的層均需拉取和推送,因?yàn)閚ode層依賴于server.js層??偠灾?,建議按修改可能性由小到大來進(jìn)行分層的排序,以優(yōu)化拉取和推送的鏡像大小。這也是為什么在例1-4中我們?cè)诳截惼渌绦蛭募跋瓤截惲藀ackage*.json文件并安裝依賴。程序員修改程序文件的頻次遠(yuǎn)高于依賴文件。

鏡像文件

安全無捷徑。構(gòu)建最終在Kubernetes生產(chǎn)集群中運(yùn)行鏡像時(shí),請(qǐng)確保遵循打包和分發(fā)應(yīng)用的最佳實(shí)踐。例如,不要在構(gòu)建的鏡像中內(nèi)置密碼-不止是最后一層,鏡像的任意一層均是如此。容器層所帶來的一個(gè)反直覺的問題是在一層中刪除的文件并不會(huì)在前置層中刪除。它仍會(huì)占據(jù)空間,任何有相關(guān)工具的人均可訪問到該文件,黑客可以輕易創(chuàng)建僅包含攜帶密碼層的鏡像。

密鑰和鏡像不應(yīng)放在一起。那樣會(huì)被黑掉的,進(jìn)行給公司或部分帶來損失。我們都想要上頭條,但使用正確的姿勢(shì)。

此外,因?yàn)槿萜麋R像聚焦于運(yùn)行單個(gè)應(yīng)用,其最佳實(shí)踐是最小化容器鏡像中的文件。鏡像中每增加一個(gè)庫都會(huì)為應(yīng)用增加一個(gè)攻擊面。根據(jù)所使用的語言,我們可以緊湊的依賴集獲取非常小的鏡像。最小的依賴集也確保了鏡像不會(huì)受到未使用庫漏洞的影響。

多階段鏡像構(gòu)建

意外構(gòu)建超大鏡像的常見方式是把實(shí)際程序編譯變成應(yīng)用程序容器鏡像構(gòu)建的一部分。把編譯代碼放到鏡像構(gòu)建中是很自然的行為,這也是通過程序構(gòu)建容器鏡像最簡(jiǎn)單的方式。但問題是這樣會(huì)遺留所有不必要的開發(fā)工具,通常非常大,隱藏在鏡像中拖慢部署速度。

為解決這一問題,Docker引入了多階段構(gòu)建,Docker不僅可產(chǎn)生單個(gè)鏡像,還可生成多個(gè)鏡像。每個(gè)鏡像被看成一個(gè)階段。對(duì)象可以從前一個(gè)階段拷貝到當(dāng)前階段。

為避免空談,我們來構(gòu)建一個(gè)示例應(yīng)用kuard。這是一個(gè)復(fù)雜的應(yīng)用,包含一個(gè)React.js前端(及其構(gòu)建流程),之后內(nèi)嵌到Go程序中。Go程序運(yùn)行React.js前端所交互的后端API。

簡(jiǎn)單的Dockerfile文件可能如下所示:

FROM golang:1.17-alpine
# Install Node and NPM
RUN apk update && apk upgrade && apk add --no-cache git nodejs bash npm

# Get dependencies for Go part of build
RUN go get -u github.com/jteeuwen/go-bindata/...
RUN go get github.com/tools/godep
RUN go get github.com/kubernetes-up-and-running/kuard

WORKDIR /go/src/github.com/kubernetes-up-and-running/kuard

# Copy all sources in
COPY . .

# This is a set of variables that the build script expects
ENV VERBOSE=0
ENV PKG=github.com/kubernetes-up-and-running/kuard
ENV ARCH=amd64
ENV VERSION=test

# Do the build. This script is part of incoming sources.
RUN build/build.sh

CMD [ "/go/bin/kuard" ]

這個(gè)Dockerfile會(huì)生成包含靜態(tài)可執(zhí)行文件的容器鏡像,但同時(shí)包含所有的Go開發(fā)工具及構(gòu)建React.js前端的工具和應(yīng)用的源代碼,在最終的應(yīng)用都不需要使用。這個(gè)鏡像經(jīng)過各層累加達(dá)500 MB以上。

查看多階段構(gòu)建的效果,使用如下Dockerfile:

# STAGE 1: Build
FROM golang:1.17-alpine AS build

# Install Node and NPM
RUN apk update && apk upgrade && apk add --no-cache git nodejs bash npm

# Get dependencies for Go part of build
RUN go get -u github.com/jteeuwen/go-bindata/...
RUN go get github.com/tools/godep
WORKDIR /go/src/github.com/kubernetes-up-and-running/kuard

# Copy all sources in
COPY . .

# This is a set of variables that the build script expects
ENV VERBOSE=0
ENV PKG=github.com/kubernetes-up-and-running/kuard
ENV ARCH=amd64
ENV VERSION=test

# Do the build. Script is part of incoming sources.
RUN build/build.sh

# STAGE 2: Deployment
FROM alpine

USER nobody:nobody
COPY --from=build /go/bin/kuard /kuard

CMD [ "/kuard" ]

這個(gè)Dockerfile會(huì)生成兩個(gè)鏡像,第一個(gè)鏡像build,包含有Go編譯器、React.js工具鏈和程序代碼,第二個(gè)鏡像deployment中僅包含編譯后的二進(jìn)制。使用多階段構(gòu)建來構(gòu)建鏡像可以減少幾百兆的容器鏡像大小,進(jìn)而大大加快部署時(shí)間,因?yàn)橥ǔ2渴鹧舆t的瓶頸是網(wǎng)絡(luò)性能。通過這一Dockerfile所生成的最終鏡像僅占約20MB。

可使用如下命令構(gòu)建、運(yùn)行此鏡像:

$ docker build -t kuard .
$ docker run --rm -p 8080:8080 kuard

在遠(yuǎn)程倉(cāng)庫存儲(chǔ)鏡像

如果僅能單機(jī)訪問容器鏡像有什么意義呢?

Kubernetes的基礎(chǔ)是Pod聲明中所描述的鏡像在集群中的每臺(tái)機(jī)器上均可訪問。一種方式是將kuard鏡像導(dǎo)出然后再到每臺(tái)機(jī)器上導(dǎo)入。如果這么管理Docker鏡像的話就太吃力了。況且手工導(dǎo)入、導(dǎo)出Docker鏡像還極容易出錯(cuò)。我們一定是拒絕的!

Docker社區(qū)的標(biāo)準(zhǔn)是將Docker鏡像存儲(chǔ)到遠(yuǎn)程倉(cāng)庫。關(guān)于Docker倉(cāng)庫也有無數(shù)種選項(xiàng),如何選擇取決于你的安全要求和協(xié)助屬性。

通常首先要決定的是用私有倉(cāng)庫還是公有倉(cāng)庫。公有倉(cāng)庫允許任何人下載倉(cāng)庫中的鏡像,而私有倉(cāng)庫需要獲取授權(quán)才能下載鏡像。選擇公有還是私有有助于理解使用的場(chǎng)景。

公有倉(cāng)庫有助于共享鏡像,因?yàn)槠渌藷o需授權(quán)、輕松地獲取到容器鏡像。我們可以容器鏡像的方式分發(fā)軟件,同時(shí)確保所有的用戶獲取的是相同的體驗(yàn)。

相反,私有倉(cāng)庫適用于私有的不希望與外界共享的服務(wù)。

不論如何,在推送鏡像時(shí)倉(cāng)庫都需要進(jìn)行認(rèn)證。通??梢允褂胐ocker login命令,但各個(gè)倉(cāng)庫有略有不同。我們示例是推送到Google云平臺(tái)倉(cāng)庫,稱為Google容器倉(cāng)庫(GCR),其它的云平臺(tái),包括Azure和AWS也都提供托管的容器倉(cāng)庫。讀者如果希望托管可供公共訪問的鏡像,可以先使用Docker Hub。

完成登錄后,可以為kuard鏡像打上前置有Docker倉(cāng)庫地址的標(biāo)簽。 也可以在冒號(hào)后(:)添加用于標(biāo)識(shí)版本或變體的信息。

$ docker tag kuard gcr.io/kuar-demo/kuard-amd64:blue

然后推送該kuard鏡像:

$ docker push gcr.io/kuar-demo/kuard-amd64:blue

現(xiàn)在已經(jīng)可以在遠(yuǎn)程倉(cāng)庫中訪問kuard鏡像了,是時(shí)候使用Docker進(jìn)行部署了。我們這里的鏡像在GCR中設(shè)置為了對(duì)公訪問,所以無需授權(quán)即可拉取。

容器運(yùn)行時(shí)接口

Kubernetes提供了一個(gè)描述應(yīng)用部署的API,但依賴于容器運(yùn)行時(shí)來使用目標(biāo)操作系統(tǒng)原生的具體容器API來配置應(yīng)用容器。在Linux操作系統(tǒng)即是配置cgroup和命名空間。容器運(yùn)行時(shí)的接口由容器運(yùn)行時(shí)接口(CRI)標(biāo)準(zhǔn)定義。CRI API由眾多程序?qū)崿F(xiàn),其中有由Docker構(gòu)建的containerd-cri和由紅帽貢獻(xiàn)的cri-o實(shí)現(xiàn)。安裝docker工具時(shí)會(huì)同時(shí)安裝containerd運(yùn)行時(shí)并由Docker daemon所使用。

從Kubernetes 1.25發(fā)行版開始,僅支持容器運(yùn)行時(shí)接口的運(yùn)行時(shí)方能在Kubernetes中使用。所幸的是Kubernetes服務(wù)商讓這種轉(zhuǎn)換對(duì)用戶幾近自動(dòng)化完成。

使用Docker運(yùn)行容器

雖然在Kubernetes中容器通常由各節(jié)點(diǎn)上稱作kubelet的守護(hù)進(jìn)程所啟動(dòng),但使用Docker命令行工具開始學(xué)習(xí)會(huì)更為方便。Docker CLI工具可用于部署容器。要通過gcr.io/kuar?demo/kuard-amd64:blue鏡像部署容器,可使用如下命令:

$ docker run -d --name kuard \
--publish 8080:8080 \
gcr.io/kuar-demo/kuard-amd64:blue

以上命令啟動(dòng)一個(gè)kuard容器并將本機(jī)的8080端口映射到容器的8080端口。--publish選項(xiàng)可簡(jiǎn)寫為-p。這種轉(zhuǎn)發(fā)是必要的,因?yàn)槊總€(gè)容器有自己的IP地址,所以監(jiān)聽容器內(nèi)的localhost并不會(huì)讓我們監(jiān)聽到本機(jī)。如果不進(jìn)行端口轉(zhuǎn)發(fā),本機(jī)上就無法進(jìn)行連接。-d參數(shù)指定它以后臺(tái)(daemon)方式運(yùn)行,而--name kuard為容器設(shè)置了一個(gè)更友好的名稱。

瀏覽kuard應(yīng)用

kuard是一個(gè)簡(jiǎn)單的web界面,可通過在瀏覽器中訪問http://localhost:8080 或在命令行中輸入:

$ curl http://localhost:8080

kuard還包含一些其它功能,我們?cè)诤罄m(xù)講解。

資源限用

Docker通過暴露Linux 內(nèi)核底層的cgroup技術(shù)提供了限制應(yīng)用所使用的資源量。這種能力Kubernetes也進(jìn)行了類似的使用,用于限制每個(gè)Pod所使用的資源。

內(nèi)存限用

在容器中運(yùn)行應(yīng)用的一大好處是可以限制所使用的資源。這讓多個(gè)應(yīng)用可共存于同一硬件設(shè)備之上并保障的公平的使用。

限制kuard使用200 MB內(nèi)存和1 GB的swap空間,可在docker run命令后添加--memory--memory-swap參數(shù)。

停止并刪除當(dāng)前的kuard容器:

$ docker stop kuard
$ docker rm kuard

重新啟動(dòng)一個(gè)kuard容器,使用相應(yīng)的參數(shù)限制內(nèi)存的使用:

$ docker run -d --name kuard \
--publish 8080:8080 \
--memory 200m \
--memory-swap 1G \
gcr.io/kuar-demo/kuard-amd64:blue

如果容器中的程序使用過多內(nèi)存的話,就會(huì)終止。

CPU限用

主機(jī)上另一個(gè)重要資源是CPU。對(duì)docker run命令使用--cpu-shares參數(shù)來限定CPU的使用:

$ docker run -d --name kuard \
--publish 8080:8080 \
--memory 200m \
--memory-swap 1G \
--cpu-shares 1024 \
gcr.io/kuar-demo/kuard-amd64:blue

清理

在完成鏡像構(gòu)建后,可通過docker rmi命令來刪除:

docker rmi <tag-name>

docker rmi <image-id>

鏡像既可通過標(biāo)簽名(如gcr.io/kuar?demo/kuard-amd64:blue)也可通過鏡像ID進(jìn)行刪除。所使用的ID精簡(jiǎn)到在docker工具內(nèi)可保持唯一的長(zhǎng)度。通常對(duì)于一個(gè)ID只輸要使用前3個(gè)或4個(gè)字母。

需要注意的是除非顯式地刪除某一鏡像,否則它會(huì)一直存在于系統(tǒng)中,哪怕是使用相同的名稱構(gòu)建了一個(gè)新鏡像。構(gòu)建新鏡像只會(huì)將標(biāo)簽移至新鏡像,并不會(huì)刪除老的鏡像。

因此,隨著你不斷地創(chuàng)建新鏡像,通常會(huì)創(chuàng)建很多不同的鏡像,進(jìn)而占據(jù)很多電腦的存儲(chǔ)空間。

要查看電腦上當(dāng)前的鏡像,可以使用docker images命令。然后可以刪除不再使用的標(biāo)簽。

Docker提供了一個(gè)docker system prune工具用于進(jìn)行日常清理。它會(huì)刪除掉所有停止的容器、未打標(biāo)簽的鏡像以及構(gòu)建遺留的未被使用的鏡像層緩存。請(qǐng)謹(jǐn)慎使用。

更高級(jí)的用法是設(shè)置一個(gè)定時(shí)任務(wù)來運(yùn)行鏡像垃圾回收器。例如,可以將docker system prune設(shè)置為一個(gè)循環(huán)的執(zhí)行的cronjob,根據(jù)所創(chuàng)建的鏡像量每天或每小時(shí)執(zhí)行一次。

小結(jié)

應(yīng)用容器為應(yīng)用提供了一個(gè)干凈的抽象,在使用Docker鏡像格式進(jìn)行打包時(shí),應(yīng)用變得易于構(gòu)建、部署和分發(fā)。容器還讓同一臺(tái)機(jī)器上的應(yīng)用保持隔離,有助于解決依賴沖突問題。

后面的文章中我們會(huì)學(xué)習(xí)如何掛載外部目錄,也就是說我們不僅可以在容器中運(yùn)行無狀態(tài)的應(yīng)用,還能運(yùn)行mysql等生成大量數(shù)據(jù)的應(yīng)用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容