1. 程式人生 > >三個技巧,將Docker映象體積減小90%

三個技巧,將Docker映象體積減小90%

在構建 Docker 容器時,應該儘量想辦法獲得體積更小的映象,因為傳輸和部署體積較小的映象速度更快。

RUN語句總是會建立一個新層,而且在生成映象之前還需要使用很多中間檔案,在這種情況下,該如何獲得體積更小的映象呢?

你可能已經注意到了,大多數 Dockerfiles 都使用了一些奇怪的技巧:

FROM ubuntu
RUN apt-get update && apt-get install vim

為什麼使用 &&?而不是使用兩個 RUN 語句代替呢?比如:

FROM ubuntu
RUN apt-get update
RUN apt-get install vim

從 Docker 1.10 開始,COPYADDRUN語句會向映象中新增新層。前面的示例建立了兩個層而不是一個。

映象的層就像 Git 的提交(commit)一樣。

Docker 的層用於儲存映象的上一版本和當前版本之間的差異。就像 Git 的提交一樣,如果你與其他儲存庫或映象共享它們,就會很方便。

實際上,當你向登錄檔請求映象時,只是下載你尚未擁有的層。這是一種非常高效地共享映象的方式。

但額外的層並不是沒有代價的。

層仍然會佔用空間,你擁有的層越多,最終的映象就越大。Git 儲存庫在這方面也是類似的,儲存庫的大小隨著層數的增加而增加,因為 Git 必須儲存提交之間的所有變更。

過去,將多個RUN語句組合在一行命令中或許是一種很好的做法,就像上面的第一個例子那樣,但在現在看來,這樣做並不妥。

通過Docker 多階段構建將多個層壓縮為一個

當 Git 儲存庫變大時,你可以選擇將歷史提交記錄壓縮為單個提交。

事實證明,在 Docker 中也可以使用多階段構建達到類似的目的。

在這個示例中,你將構建一個 Node.js 容器。

讓我們從 index.js 開始:

const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(3000, () => {
  console.log(`Example app listening on port 3000!`)
})

和 package.json:

{
  "name": "hello-world",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "express": "^4.16.2"
  },
  "scripts": {
    "start": "node index.js"
  }
}

你可以使用下面的 Dockerfile 來打包這個應用程式:

FROM node:8
EXPOSE 3000
WORKDIR /app
COPY package.json index.js ./
RUN npm install
CMD ["npm", "start"]
FROM node:10
MAINTAINER xialeistudio [email protected]
WORKDIR /usr/src/app
ENV TZ Asia/Shanghai
ARG registry=https://registry.npm.taobao.org
ARG disturl=https://npm.taobao.org/dist
RUN yarn config set disturl $disturl
RUN yarn config set registry $registry
COPY package.json /usr/src/app/
RUN yarn --frozen-lockfile --production
COPY . /usr/src/app
EXPOSE 8080
CMD [ "yarn", "start:prod" ]

然後開始構建映象:

$ docker build -t node-vanilla .

然後用以下方法驗證它是否可以正常執行:

$ docker run -p 3000:3000 -ti --rm --init node-vanilla

你應該能訪問 http://localhost:3000,並收到“Hello World!”。

Dockerfile 中使用了一個 COPY 語句和一個 RUN 語句,所以按照預期,新映象應該比基礎映象多出至少兩個層:

$ docker history node-vanilla
IMAGE          CREATED BY                                      SIZE
075d229d3f48   /bin/sh -c #(nop)  CMD ["npm" "start"]          0B
bc8c3cc813ae   /bin/sh -c npm install                          2.91MB
bac31afb6f42   /bin/sh -c #(nop) COPY multi:3071ddd474429e1…   364B
500a9fbef90e   /bin/sh -c #(nop) WORKDIR /app                  0B
78b28027dfbf   /bin/sh -c #(nop)  EXPOSE 3000                  0B
b87c2ad8344d   /bin/sh -c #(nop)  CMD ["node"]                 0B
<missing>      /bin/sh -c set -ex   && for key in     6A010…   4.17MB
<missing>      /bin/sh -c #(nop)  ENV YARN_VERSION=1.3.2       0B
<missing>      /bin/sh -c ARCH= && dpkgArch="$(dpkg --print…   56.9MB
<missing>      /bin/sh -c #(nop)  ENV NODE_VERSION=8.9.4       0B
<missing>      /bin/sh -c set -ex   && for key in     94AE3…   129kB
<missing>      /bin/sh -c groupadd --gid 1000 node   && use…   335kB
<missing>      /bin/sh -c set -ex;  apt-get update;  apt-ge…   324MB
<missing>      /bin/sh -c apt-get update && apt-get install…   123MB
<missing>      /bin/sh -c set -ex;  if ! command -v gpg > /…   0B
<missing>      /bin/sh -c apt-get update && apt-get install…   44.6MB
<missing>      /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>      /bin/sh -c #(nop) ADD file:1dd78a123212328bd…   123MB

但實際上,生成的映象多了五個新層:每一個層對應 Dockerfile 裡的一個語句。

現在,讓我們來試試 Docker 的多階段構建。

你可以繼續使用與上面相同的 Dockerfile,只是現在要呼叫兩次:

FROM node:8 as build
WORKDIR /app
COPY package.json index.js ./
RUN npm install
FROM node:8
COPY --from=build /app /
EXPOSE 3000
CMD ["index.js"]

Dockerfile 的第一部分建立了三個層,然後這些層被合併並複製到第二個階段。在第二階段,映象頂部又添加了額外的兩個層,所以總共是三個層。

現在來驗證一下。首先,構建容器:

$ docker build -t node-multi-stage .

檢視映象的歷史:

$ docker history node-multi-stage
IMAGE          CREATED BY                                      SIZE
331b81a245b1   /bin/sh -c #(nop)  CMD ["index.js"]             0B
bdfc932314af   /bin/sh -c #(nop)  EXPOSE 3000                  0B
f8992f6c62a6   /bin/sh -c #(nop) COPY dir:e2b57dff89be62f77…   1.62MB
b87c2ad8344d   /bin/sh -c #(nop)  CMD ["node"]                 0B
<missing>      /bin/sh -c set -ex   && for key in     6A010…   4.17MB
<missing>      /bin/sh -c #(nop)  ENV YARN_VERSION=1.3.2       0B
<missing>      /bin/sh -c ARCH= && dpkgArch="$(dpkg --print…   56.9MB
<missing>      /bin/sh -c #(nop)  ENV NODE_VERSION=8.9.4       0B
<missing>      /bin/sh -c set -ex   && for key in     94AE3…   129kB
<missing>      /bin/sh -c groupadd --gid 1000 node   && use…   335kB
<missing>      /bin/sh -c set -ex;  apt-get update;  apt-ge…   324MB
<missing>      /bin/sh -c apt-get update && apt-get install…   123MB
<missing>      /bin/sh -c set -ex;  if ! command -v gpg > /…   0B
<missing>      /bin/sh -c apt-get update && apt-get install…   44.6MB
<missing>      /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>      /bin/sh -c #(nop) ADD file:1dd78a123212328bd…   123MB

檔案大小是否已發生改變?

$ docker images | grep node-
node-multi-stage   331b81a245b1   678MB
node-vanilla       075d229d3f48   679MB

最後一個映象(node-multi-stage)更小一些。

你已經將映象的體積減小了,即使它已經是一個很小的應用程式。

但整個映象仍然很大!

有什麼辦法可以讓它變得更小嗎?

用 distroless 去除不必要的東西

這個映象包含了 Node.js 以及 yarn、npm、bash 和其他的二進位制檔案。因為它也是基於 Ubuntu 的,所以你等於擁有了一個完整的作業系統,其中包括所有的小型二進位制檔案和實用程式。

但在執行容器時是不需要這些東西的,你需要的只是 Node.js。

Docker 容器應該只包含一個程序以及用於執行這個程序所需的最少的檔案,你不需要整個作業系統。

實際上,你可以刪除 Node.js 之外的所有內容。

但要怎麼做?

所幸的是,谷歌為我們提供了 distroless(https://github.com/GoogleCloudPlatform/distroless)。

以下是 distroless 儲存庫的描述:

“distroless”映象只包含應用程式及其執行時依賴項,不包含程式包管理器、shell 以及在標準 Linux 發行版中可以找到的任何其他程式。

這正是你所需要的!

你可以對 Dockerfile 進行調整,以利用新的基礎映象,如下所示:

FROM node:8 as build
WORKDIR /app
COPY package.json index.js ./
RUN npm install
FROM gcr.io/distroless/nodejs
COPY --from=build /app /
EXPOSE 3000
CMD ["index.js"]

你可以像往常一樣編譯映象:

$ docker build -t node-distroless .

這個映象應該能正常執行。要驗證它,可以像這樣執行容器:

$ docker run -p 3000:3000 -ti --rm --init node-distroless

現在可以訪問 http://localhost:3000 頁面。

不包含其他額外二進位制檔案的映象是不是小多了?

$ docker images | grep node-distroless
node-distroless   7b4db3b7f1e5   76.7MB

只有 76.7MB!

比之前的映象小了 600MB!

但在使用 distroless 時有一些事項需要注意。

當容器在執行時,如果你想要檢查它,可以使用以下命令 attach 到正在執行的容器上:

$ docker exec -ti <insert_docker_id> bash

attach 到正在執行的容器並執行 bash 命令就像是建立了一個 SSH 會話一樣。

但 distroless 版本是原始作業系統的精簡版,沒有了額外的二進位制檔案,所以容器裡沒有 shell!

在沒有 shell 的情況下,如何 attach 到正在執行的容器呢?

答案是,你做不到。這既是個壞訊息,也是個好訊息。

之所以說是壞訊息,因為你只能在容器中執行二進位制檔案。你可以執行的唯一的二進位制檔案是 Node.js:

$ docker exec -ti <insert_docker_id> node

說它是個好訊息,是因為如果攻擊者利用你的應用程式獲得對容器的訪問許可權將無法像訪問 shell 那樣造成太多破壞。換句話說,更少的二進位制檔案意味著更小的體積和更高的安全性,不過這是以痛苦的除錯為代價的。

或許你不應在生產環境中 attach 和除錯容器,而應該使用日誌和監控。

但如果你確實需要除錯,又想保持小體積該怎麼辦?

小體積的 Alpine 基礎映象

你可以使用 Alpine 基礎映象替換 distroless 基礎映象。

Alpine Linux 是:

一個基於 musl libc 和 busybox 的面向安全的輕量級 Linux 發行版。

換句話說,它是一個體積更小也更安全的 Linux 發行版。

不過你不應該理所當然地認為他們聲稱的就一定是事實,讓我們來看看它的映象是否更小。

先修改 Dockerfile,讓它使用 node:8-alpine:

FROM node:8 as build
WORKDIR /app
COPY package.json index.js ./
RUN npm install
FROM node:8-alpine
COPY --from=build /app /
EXPOSE 3000
CMD ["npm", "start"]

使用下面的命令構建映象:

$ docker build -t node-alpine .

現在可以檢查一下映象大小:

$ docker images | grep node-alpine
node-alpine   aa1f85f8e724   69.7MB

69.7MB!

甚至比 distrless 映象還小!

現在可以 attach 到正在執行的容器嗎?讓我們來試試。

讓我們先啟動容器:

$ docker run -p 3000:3000 -ti --rm --init node-alpine
Example app listening on port 3000!

你可以使用以下命令 attach 到執行中的容器:

$ docker exec -ti 9d8e97e307d7 bash
OCI runtime exec failed: exec failed: container_linux.go:296: starting container process caused "exec: \"bash\": executable file not found in $PATH": unknown

看來不行,但或許可以使用 shell?

$ docker exec -ti 9d8e97e307d7 sh / #

成功了!現在可以 attach 到正在執行的容器中了。

看起來很有希望,但還有一個問題。

Alpine 基礎映象是基於 muslc 的——C 語言的一個替代標準庫,而大多數 Linux 發行版如 Ubuntu、Debian 和 CentOS 都是基於 glibc 的。這兩個庫應該實現相同的核心介面。

但它們的目的是不一樣的:

  • glibc 更常見,速度也更快;

  • muslc 使用較少的空間,並側重於安全性。

在編譯應用程式時,大部分都是針對特定的 libc 進行編譯的。如果你要將它們與另一個 libc 一起使用,則必須重新編譯它們。

換句話說,基於 Alpine 基礎映象構建容器可能會導致非預期的行為,因為標準 C 庫是不一樣的。

你可能會注意到差異,特別是當你處理預編譯的二進位制檔案(如 Node.js C++ 擴充套件)時。

例如,PhantomJS 的預構建包就不能在 Alpine 上執行。

你應該選擇哪個基礎映象?

你應該使用 Alpine、distroless 還是原始映象?

如果你是在生產環境中執行容器,並且更關心安全性,那麼可能 distroless 映象更合適。

新增到 Docker 映象的每個二進位制檔案都會給整個應用程式增加一定的風險。

只在容器中安裝一個二進位制檔案可以降低總體風險。

例如,如果攻擊者能夠利用執行在 distroless 上的應用程式的漏洞,他們將無法在容器中使用 shell,因為那裡根本就沒有 shell!

請注意,OWASP 本身就建議儘量減少攻擊表面。

如果你只關心更小的映象體積,那麼可以考慮基於 Alpine 的映象。

它們的體積非常小,但代價是相容性較差。Alpine 使用了略微不同的標準 C 庫——muslc。你可能會時不時地遇到一些相容性問題。

原始基礎映象非常適合用於測試和開發。

它雖然體積很大,但提供了與 Ubuntu 工作站一樣的體驗。此外,你還可以訪問作業系統的所有二進位制檔案。

相關推薦

6、CentOS7 安裝Docker之擴充套件(技巧Docker映象體積減小90%)

三個技巧,將Docker映象體積減小90% 在構建Docker容器時,應該儘量想辦法獲得體積更小的映象,因為傳輸和部署體積較小的映象速度更快。 但RUN語句總是會建立一個新層,而且在生成映象之前還需要使用很多中間檔案,在這種情況下,該如何獲得體積更小的映象呢? 你可能

Docker學習總結(41)——技巧Docker映象體積減小90%

一、前言 在構建Docker容器時,應該儘量想辦法獲得體積更小的映象,因為傳輸和部署體積較小的映象速度更快。但RUN語句總是會建立一個新層,而且在生成映象之前還需要使用很多中間檔案,在這種情況下,該如何獲得體積更小的映象呢?你可能已經注意到了,大多數Dockerfiles都

技巧Docker映象體積減小90%

在構建 Docker 容器時,應該儘量想辦法獲得體積更小的映象,因為傳輸和部署體積較小的映象速度更快。 但RUN語句總是會建立一個新層,而且在生成映象之前還需要使用很多中間檔案,在這種情況下,該如何獲得體積更小的映象呢? 你可能已經注意到了,大多數 Dockerfiles

奇技淫巧 Docker 映象體積減小 99%

原文連結:Docker Images : Part I - Reducing Image Size 對於剛接觸容器的人來說,他們很容易被自己構建的 Docker 映象體積嚇到,我只需要一個幾 MB 的可執行檔案而已,為何映象的體積會達到 1 GB 以上?本文將會介紹幾個奇技淫巧來幫助你精簡映象,同時又不犧

減小docker映象體積的3技巧

轉載自:http://www.infoq.com/cn/articles/3-simple-tricks-for-smaller-docker-images?utm_campaign=rightbar_v2&utm_source=infoq&utm_medium=articles_l

輸入10整數其中最小的數與第一個數對換把最大的數與最後一個數對換。寫函式; ①輸入10個數;②進行處理;③輸出10個數。

import java.util.Scanner; public class Main {     public static void main(String[] args){         Scanner sc = new Scann

輸入輸出流讀取本機txt文件:不能中文字元流轉化為char 因為char一個位元組中文在UTF-8的情況下是位元組會出現字元流擷取

package interview; import org.junit.Test; import java.io.*; public class TestInOrOutStream { class m { @Test void x(){ Sys

「華為手機小技巧」別看小小用途超乎你想象

現在我們追求手機有著極高的要求,要續航能力強、流暢度高、價效比高等。確實,隨著科技的不斷髮展,各個手機行業都在往更高的品質走。每個手機都隱藏著強大的功能,就拿華為手機的三個按鍵來說,有著不一樣的作用呢。一起來看看吧。 今天我們說說華為手機的三個物理鍵,那就是音量鍵+、音量鍵-以及開關機鍵。千萬別

ACMNO.37 C語言-數字交換 輸入10整數其中最小的數與第一個數對換然後把最大的數與最後一個數對換。寫函式; ①輸入10個數;②進行處理;③輸出10個數。

題目描述 輸入10個整數,將其中最小的數與第一個數對換,然後把最大的數與最後一個數對換。 寫三個函式; ①輸入10個數;②進行處理;③輸出10個數。 輸入 10個整數 輸出 整理後的十個數,每個數後跟一個空格(注意最後一個數後也有空格) 樣例輸入 2 1

又有實錘Excel與超級表格的技巧

招式一:輸入當天日期我們在製作excel表格的時候,都少不了需要輸入當天的日期,比如說2017-10-1。我們要是按照數字來輸入會非常麻煩,今天我來告訴你們一個在excel中輸入當天日期的小技巧。Excel操作步驟:1、首先選中需輸入當天日期的單元格;2、按住 “【ctrl+

C語言中可變參數的函數(“...”)

stdarg url title amp 至少 關閉 .com temp () C語言中可變參數的函數(三個點,“...”)   本文主要介紹va_start和va_end的使用及原理。   在以前的一篇帖子Format MessageBox 詳解中曾使用到va_st

忠告讓李連傑與馬雲並肩同行至今

支付 勵誌 概念 中國 並且 做的 並不是 原因 視頻 曾經有個青年馬雲的視頻瘋傳一時。 1995年,杭州某電視臺突發奇想測試人品,到馬路上把井蓋撬開,看有沒有人站出來制止。一天試下來,只有一個騎著自行車的青年大喝了一句“給我擡回去”。 這個青年就是馬雲。網友因此贊聲一篇,

安裝手機看VIP電影。

找到 eight fire firefox 分享 temp body 提示 monk 三個安裝,看VIP電影。 市場安裝firefox 安裝Tempermonkey 打開firefox,點擊右上角的三個點,點擊附加組件 繼續點擊瀏覽全部firefox附加組件 在上面的

AI手機攝影的流派其中藏了假的

假AI在咱們身邊,有一個鐵打的定律,那就是一個東西或者名稱火了,馬上就會群起而模仿之。久而久之,這種“微創新”甚至“不用創新”的方式,被國人冠名為“山寨文化”。互聯網領域則更是如此,團購火了能百團大戰,O2O火了能全面“開O”,共享經濟火了,楞是連男友女友都能“共享”。山寨得多,就連技術都可以山寨。比如說,去

本人實驗即知意念的力量有多大及意念的因果報應

很多 等等 擁有 在哪裏 AD 還得 borde 垃圾桶 原理 網友提出了一個三個月的意念實驗、以觀察結果的好辦法,來幫助我們認識我們的意念到底會產生什麽樣的力量!這個方法真是太妙了! 註意:不管你詛咒別人,還是慈愛別人的時候,意念越強烈越好,三個月後,筆者

numpy_數組(無冒號單冒號雙冒號)

num span ... pre style IV AR color col import numpy >>> a = numpy.array([[1,2,3,4,5],[6,7,8,9,10],[1,2,3,4,5],[6,7,8,9,10]]) &g

服務器兩網卡插兩根網線卻有ip求解這是什麽梗

1.9 網線 13.10 靜態ip 分別是 虛擬 type 3.1 ima 2018-09-23公司的一臺HP的服務器上,兩個網卡設置,插了兩個網線,但是卻有三個ip。不解,希望來個大神指點迷津如下圖第一個圖是該機器上的網卡,兩個網卡和一個回環接口的 第二幅圖是第一個網卡的

給出面積求三角形的任意gcd 用來約分

面積 www. a* 條件 span char 都是 pro nbsp http://codeforces.com/contest/1058/problem/D 條件 1. 給出面積m*n/k 2. 0≤x1,x2,x3≤n 0≤y1,y2,y3≤m 3

java:接受用戶從鍵盤輸入的整數並輸出最大值和最小值

scan out sca system () 最小值 public tin 代碼 import java.util.Scanner 從鍵盤輸入要在開頭聲明這個。 Scnner in=new Scanner(Syetem.in); 聲明Scnner類型的in 如果要從鍵盤輸入

備考順利通過PMP

信息 三個月 一是 博士 日報 考試 讓我 完成 詳細 我是來自寧夏銀川市的IT一員,從事IT行業10年,從事IT管理6年,一直沒有參加PMP考試,一是沒有時間,二是沒有自信,今年在網上和51CTO的老師聊了好長時間,王老師上課的魔性聲音,以及51CTO學校的口碑,讓我決定