因爲 go 最終是編譯爲一個二進制可執行文件,沒有運行時依賴,也不須要管理庫,丟到服務器上就能夠直接運行。因此,若是你有一個二進制文件,那麼在容器中打包二進制文件的要點是什麼?若是使用 docker 的話,還得在服務器上裝 docker,那麼把最終程序打包成 docker 有什麼好處呢?html
我想有這麼幾個好處:node
依賴打包
若是你的應用程序(二進制文件)依賴配置文件或一些靜態文件,那使用docker就很方便的把這些文件一塊兒打包進容器裏。python
版本控制
啓動Docker就和運行一個進程同樣快,咱們能夠在幾秒鐘的時間內運行整個服務器集羣。除此以外,Docker 鏡像的註冊中心使Docker容器還能夠像git倉庫同樣,可讓你提交變動到Docker鏡像中,並經過不一樣的版原本管理它們。設想若是你由於完成了一個組件的升級而致使你整個環境都損壞了,Docker可讓你輕鬆地回滾到這個鏡像的前一個版本。這整個過程能夠在幾分鐘內完成。mysql
隔離性
容器包含了應用程序的代碼、運行環境、依賴庫、配置文件等必需的資源。容器之間達到進程級別的隔離,在容器中的操做,不會影響道宿主機和其餘容器,這樣就不會出現應用之間相互影響的情形!linux
可移植性
能夠實現開發、測試和生產環境的統一化和標準化。鏡像做爲標準的交付件,可在開發、測試和生產環境上以容器來運行,最終實現三套環境上的應用以及運行所依賴內容的徹底一致。在如今微服務的架構中,一個應用拆成幾十個微服務,每一個微服務都對應有開發、測試、生產三套環境須要搭建。本身算算,若是採用傳統的部署方式,有多少環境須要部署。nginx
輕量和高效
和虛擬機相比,容器僅須要封裝應用和應用須要的依賴文件,實現輕量的應用運行環境,且擁有比虛擬機更高的硬件資源利用率。在微服務架構中,有些服務負載壓力大,須要以集羣部署,可能要部署幾十臺機器上,對於某些中小型公司來講,使用虛擬機,代價太大。若是用容器,一樣的物理機則能支持上千個容器,對中小型公司來講,省錢!git
安全性
Docker容器不能窺視運行在其餘容器中的進程。從體系結構角度來看,每一個容器只使用着本身的資源(從進程到網絡堆棧)。做爲緊固安全的一種手段,Docker將宿主機操做系統上的敏感掛載點(例如/proc和/sys)做爲只讀掛載點,而且使用一種寫時複製系統來確保容器不能讀取其餘容器的數據。Docker也限制了宿主機操做系統上的一些系統調用,而且和SELinux與AppArmor一塊兒運行的很好。此外,在Docker Hub上可使用的Docker鏡像都經過數字簽名來確保其可靠性。因爲Docker容器是隔離的,而且資源是受限制的,因此即便你其中一個應用程序被黑,也不會影響運行在其它Docker容器上的應用程序。github
對於許多編程語言(包括 Go ),有幾個很好的官方和社區支持的容器。咱們在容器化Go apps的時候,能夠選擇基於 Golang 官方鏡像構建,如:golang:onbuild,golang:latest。可是這有一個很大的缺點:這些容器可能很大,因此基於它們的鏡像建立的鏡像文件將會很是大。golang
這是由於咱們的應用程序是在容器內編譯的。這意味着該容器須要安裝 Go ,以及 Go 的依賴關係,同時這也意味着咱們須要一個程序包管理器和整個操做系統。實際上,若是您查看 Golang 的 Dockerfile,它將以 Debian Jessie 開頭,安裝 GCC 編譯器和一些構建工具,壓縮 Go 並安裝它。所以,咱們幾乎有一個完整的 Debian 服務器和 Go 工具包來運行咱們的小型應用程序。redis
因此咱們應該使用一種靜態構建 Go 容器化應用的方法,這種方法生成的鏡像文件很是小。
我以 Passport 應用爲例,下面是應用程序結構:
$ tree -L 2 . ├── Makefile ├── control ├── app │ ├── boot │ ├── kernel │ ├── lib │ ├── main.go │ ├── server ├── output │ └── bin └── vendor ├── appengine ├── cloud.google.com ├── github.com ├── go.etcd.io ├── go.uber.org ├── golang.org ├── golang_org ├── google.golang.org ├── gopkg.in └── vendor.json
咱們要作的是在工做目錄中編譯 Go ,而後將二進制文件添加到容器中。這種方式比直接使用官方鏡像麻煩一些,不過體積要小不少,因此建議這麼作。
go build -o output/bin/go_service_passport ./app
首先,在項目的根目錄下,新建一個文本文件.dockerignore,寫入下面的內容。
# comment .git .gitignore output *.out *.log */temp* */*/temp* temp? *.md !README.md
在Docker CLI將上下文發送到Docker守護程序以前,它將在上下文的根目錄中查找名爲.dockerignore的文件。若是此文件存在,則CLI會修改上下文以排除與其中的模式匹配的文件和目錄。這有助於避免沒必要要地將大型文件或敏感文件和目錄發送到守護程序,並避免使用ADD或COPY將它們添加到映像中。
若是.dockerignore文件中的行以第1列中的#開頭,則該行將被視爲註釋,而且在CLI解釋以前將被忽略。
官方說明見:.dockerignore file
接下來就是編寫 Dockerfile 文件了。Dockerfile是一個文本文檔,Docker能夠經過閱讀Dockerfile中的指令來自動構建映像。
該文檔內的指令不區分大小寫。可是,習慣是大寫,以便更輕鬆地將它們與參數區分開。
Docker 按順序在 Dockerfile 中運行指令。 Dockerfile 必須以 「FROM」 指令開頭。固然,FROM 前面能夠有一個或多個 ARG 指令或註釋,ARG 指令聲明 Dockerfile 中 FROM 行中使用的參數。
在開始以前咱們應該想清楚到底使用哪一個基礎鏡像?通常狀況下,都會從如下三個基礎鏡像開始。
So what’s scratch? Scratch is a special docker image that’s empty. It’s truly 0B:
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE scratch latest 511136ea3c5a 22 months ago 0 B
目前 Docker 官方已開始推薦使用 Alpine 替代以前的 Ubuntu 作爲基礎鏡像環境。這樣會帶來多個好處。包括鏡像下載速度加快,鏡像安全性提升,主機之間的切換更方便,佔用更少磁盤空間等。
下面,不如以上三個鏡像咱們都嘗試一下吧。在項目的根目錄下,新建一個文本文件 Dockerfile,寫入下面的內容。
FROM scratch LABEL maintainer="tobeabme@gmail.com" version="1.0" ENV RUNMODE dev ENV CONSUL_ADDR 127.0.0.1:8500 ENV ETCD_ADDR 127.0.0.1:2379 ADD output/bin/go_service_passport / EXPOSE 8080 9080 CMD ["/go_service_passport"]
下面這種寫法是錯誤的,爲何呢?
FROM scratch MAINTAINER weizi ENV APP_RUN_DIR /data/app/go/work ENV APP_LOG_DIR /data/app/go/log RUN mkdir -p ${APP_RUN_DIR} \ && mkdir -p ${APP_LOG_DIR} COPY output/bin/go_service_passport ${APP_RUN_DIR} WORKDIR ${APP_RUN_DIR} EXPOSE 8080 9080 CMD ["${APP_RUN_DIR}/go_service_passport"]
這是因爲 scratch 鏡像幾乎不包含任何東西,不支持環境變量,也沒有 shell 命令。 所以,基於 scratch 的鏡像經過 ADD 指令進行添加,以此繞過目錄建立。更完整的緣由說明見以下:
FROM scratch is a completely empty filesystem. You have no installed libraries, and no shell (like /bin/sh) included in there. To use this as your base, you'd need a statically linked binary, or you'll need to install all of the normal tools that are included with a linux distribution.
The latter is what is prepackaged in the various busybox, debian, ubuntu, centos, etc images on the docker hub. The fast way to make your image work with a minimal base image is to change the from to FROM busybox and change your /bin/bash to /bin/sh.
FROM busybox MAINTAINER weizi ENV APP_RUN_DIR /data/app/go/work ENV RUNMODE dev ENV CONSUL_ADDR 127.0.0.1:8500 ENV ETCD_ADDR 127.0.0.1:2379 #RUN mkdir -p /data/app/go/work WORKDIR $APP_RUN_DIR ADD output/bin/go_service_passport . EXPOSE 8080 9080 CMD ["./go_service_passport","-g","daemon off;"]
ARG GO_VERSION=1.10.3 FROM golang:${GO_VERSION} AS builder MAINTAINER weizi LABEL author="name@gmail.com" ENV GO111MODULE=off ENV GO15VENDOREXPERIMENT=1 WORKDIR $GOPATH/src/code.qschou.com/peduli/go_service_passport COPY . . RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o output/bin/go_service_passport ./app #------------------------------------------ FROM alpine MAINTAINER weizi LABEL author="name@gmail.com" ENV APP_RUN_DIR /data/app/go/work ENV RUNMODE dev ENV CONSUL_ADDR 127.0.0.1:8500 ENV ETCD_ADDR 127.0.0.1:2379 RUN apk update \ && apk --no-cache add wget ca-certificates \ && apk add -f --no-cache git \ && apk add -U tzdata \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime WORKDIR $APP_RUN_DIR COPY --from=builder /go/src/code.qschou.com/peduli/go_service_passport/output/bin/go_service_passport . EXPOSE 8080 9080 CMD ["./go_service_passport","-g","daemon off;"]
最後,讓咱們看下上面三種鏡像生成後的大小:
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE passport-busybox 1.0.1 1028fbd88847 32 seconds ago 36.3MB passport-scratch 1.0.1 aa407fee8d95 33 minutes ago 35.1MB passport-multi-stage 1.0.9 dd8a070d96e9 2 days ago 59.4MB
Go應用自己的二進制文件爲33MB
$ ls -lh output/bin/go_service_passport -rwxr-xr-x 1 will staff 33M Nov 18 11:09 output/bin/go_service_passport
scratch size = 35.1-33 = 2.1M
busybox size = 36.3-33 = 3.3M
alpine size = 59.4-33 = 26.1M
FROM指令初始化一個新的構建階段,併爲後續指令設置基本映像。所以,有效的 Dockerfile 必須以 FROM 指令開頭。
格式:
FROM <image> [AS <name>] Or FROM <image>[:<tag>] [AS <name>] Or FROM <image>[@<digest>] [AS <name>]
MAINTAINER指令設置生成圖像的「做者」字段。 LABEL指令是此指令的更爲靈活的版本,您應該使用它,由於它能夠設置所需的任何元數據,而且能夠輕鬆查看,例如使用docker inspect。 要設置與MAINTAINER字段相對應的標籤,可使用:
LABEL maintainer="SvenDowideit@home.org.au"
LABEL指令將元數據添加到圖像。 標籤是鍵值對。 要在LABEL值中包含空格,請使用引號和反斜槓。 一些用法示例:
LABEL "com.example.vendor"="ACME Incorporated" LABEL description="This text illustrates \ that label-values can span multiple lines."
一個鏡像能夠有多個標籤。 如下兩種方式之一在一條指令中指定多個標籤:
基本或父圖像(FROM行中的圖像)中包含的標籤由您的圖像繼承。 若是標籤已經存在但具備不一樣的值,則最近應用的值將覆蓋任何先前設置的值。
LABEL multi.label1="value1" multi.label2="value2" other="value3" OR LABEL multi.label1="value1" \ multi.label2="value2" \ other="value3"
被包含在基礎鏡像或父鏡像(images in the FROM line)的 Labels 是被你的鏡像繼承的。若是一個標籤已經存在,但具備不一樣的值,則最近應用的值將覆蓋任何先前設置的值。
去查看一個鏡像的 Labels,請使用docker inspect命令。
設置環境變量。將環境變量<key>設置爲值<value>。 此值將在構建階段中全部後續指令(RUN、ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD)的環境中使用。
格式:
ENV <key> <value> ENV <key>=<value> ...
第一種形式,ENV <鍵> <值>,將單個變量設置爲一個值。 第一個空格以後的整個字符串將被視爲<value>-包括空格字符。 該值能夠被其餘環境變量解釋,所以若是不對引號字符進行轉義,則將其刪除。
第二種格式,ENV <key> = <value> ...,容許一次設置多個變量。 請注意,第二種形式在語法中使用等號(=),而第一種形式則不使用等號(=)。 與命令行解析同樣,引號和反斜槓可用於在值中包含空格。
For example:
ENV myName="John Doe" myDog=Rex\ The\ Dog \ myCat=fluffy and ENV myName John Doe ENV myDog Rex The Dog ENV myCat fluffy
當一個容器是從產生的鏡像運行時,使用ENV設置的環境變量將持續存在。您可使用docker inspect查看值,並使用docker run --env <key> = <value>更改它們。
環境變量(用ENV語句聲明)也能夠在某些指令中用做Dockerfile解釋的變量。經過在字面上將相似變量的語法包含到語句中來處理。
在 Dockerfile 文檔中,可使用 $variable_name 或 ${variable_name} 引用環境變量,它們是等同的。其中大括號的變量是用在沒有空格的變量名中的,如${foo}_bar。
${variable_name}變量也支持一些標準的bash修飾符,如:
word能夠是任意的字符,包括額外的環境變量。
轉義符(Escaping)能夠添加在變量前面:$foo or ${foo},例如,會分別轉換爲$foor和${foo}。示例:
FROM busybox ENV foo /bar WORKDIR ${foo} # WORKDIR /bar ADD . $foo # ADD . /bar COPY \$foo /quux # COPY $foo /quux
在此實例中:
ENV abc=hello ENV abc=bye def=$abc ENV ghi=$abc
將致使def的值爲hello,而不是bye。可是,ghi的值是bye。
Dockerfile中的如下指令列表支持環境變量:
官方說明見: Environment replacement
ARG指令定義了一個變量,用戶能夠在構建時使用--build-arg <varname> = <value>標誌使用docker build命令將其傳遞給構建器。若是用戶指定了未在Dockerfile中定義的構建參數,則構建會輸出警告。
[Warning] One or more build-args [foo] were not consumed.
格式:
ARG <name>[=<default value>]
Dockerfile可能包含一個或多個ARG指令。例如,
FROM busybox ARG user1 ARG buildno ...
警告:不建議使用構建時變量來傳遞諸如github密鑰,用戶憑據等機密。構建時變量值對於使用docker history命令的映像的任何用戶都是可見的。
默認值
ARG指令能夠選擇包含默認值:
FROM busybox ARG user1=someuser ARG buildno=1 ...
若是ARG指令具備默認值,而且在構建時未傳遞任何值,則構建器將使用默認值。
ARG指令在定義它的構建階段結束時超出範圍。要在多個階段中使用arg,每一個階段都必須包含ARG指令。
FROM busybox ARG SETTINGS RUN ./run/setup $SETTINGS FROM busybox ARG SETTINGS RUN ./run/other $SETTINGS
使用ARG變量
您可使用ARG或ENV指令來指定RUN指令可用的變量。使用ENV指令定義的環境變量始終會覆蓋同名的ARG指令。
1 FROM ubuntu 2 ARG CONT_IMG_VER 3 ENV CONT_IMG_VER v1.0.0 4 RUN echo $CONT_IMG_VER
而後,假定此映像是使用如下命令構建的:
$ docker build --build-arg CONT_IMG_VER=v2.0.1 .
在這種狀況下,RUN指令使用v1.0.0而不是用戶傳遞的ARG設置:v2.0.1此行爲相似於shell腳本,其中局部做用域的變量會覆蓋從參數傳遞過來的做爲參數。
使用上面的示例,但使用不一樣的ENV規範,您能夠在ARG和ENV指令之間建立更有用的交互:
1 FROM ubuntu 2 ARG CONT_IMG_VER 3 ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0} 4 RUN echo $CONT_IMG_VER
與ARG指令不一樣,ENV值始終保留在生成的映像中。考慮不帶--build-arg標誌的Docker構建:
$ docker build .
使用此Dockerfile示例,CONT_IMG_VER仍保留在映像中,但其值爲v1.0.0,由於它是ENV指令在第3行中設置的默認值。
在此示例中,變量擴展技術使您能夠從命令行傳遞參數,並利用ENV指令將其保留在最終映像中。
預約義的ARG
Docker具備一組預約義的ARG變量,您能夠在Dockerfile中使用它們而無需相應的ARG指令。
默認狀況下,這些預約義變量從docker history記錄的輸出中排除。排除它們能夠下降意外泄漏HTTP_PROXY變量中的敏感身份驗證信息的風險。
--build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com
COPY指令從<src>複製新文件或目錄,並將它們添加到容器的文件系統中,路徑爲<dest>。因爲咱們這裏是拷貝Go構建好的二進制文件,因此不用將當前目錄下的全部文件(除了.dockerignore排除的路徑),都拷貝進入 image 文件的 $WORKPATH 目錄。若是須要拷貝後自動解壓,用 ADD 指令。
COPY有兩種形式:
COPY [--chown=<user>:<group>] <src>... <dest> COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] (this form is required for paths containing whitespace)
每一個<src>均可以包含通配符,而且將使用Go的filepath.Match規則進行匹配。例如:
COPY hom* /mydir/ # adds all files starting with "hom" COPY hom?.txt /mydir/ # ? is replaced with any single character, e.g., "home.txt"
<dest>是絕對路徑,或相對於WORKDIR的路徑,源將在目標容器內複製到該路徑。
COPY test relativeDir/ # adds "test" to `WORKDIR`/relativeDir/ COPY test /absoluteDir/ # adds "test" to /absoluteDir/
複製包含特殊字符 (such as [ and ]), 的文件或目錄時,須要遵循Golang規則轉義那些路徑,以防止將它們視爲匹配模式。例如,要複製名爲 arr[0].txt 的文件,請使用如下命令:
COPY arr[[]0].txt /mydir/ # copy a file named "arr[0].txt" to /mydir/
可選地,COPY接受 --from=<name|index> 標誌,該標誌可用於將源位置設置爲先前的構建階段 (created with FROM .. AS <name>) ,該階段將用於代替由發送的構建上下文用戶。若是找不到具備指定名稱的構建階段,則嘗試改用具備相同名稱的圖像。
COPY遵照如下規則:
官方說明見:COPY
ADD指令從<src>複製新文件,目錄或遠程文件URL,並將它們添加到鏡像的文件系統中的路徑<dest>。
ADD有兩種形式:
ADD [--chown=<user>:<group>] <src>... <dest> ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] (this form is required for paths containing whitespace)
ADD遵照如下規則:
其它規則與COPY相同,不重複描述。
WORKDIR指令爲Dockerfile中跟在其後的全部RUN,CMD,ENTRYPOINT,COPY和ADD指令設置工做目錄。至關於 cd 。如該目錄不存在,WORKDIR 會幫你創建目錄,即便隨後的Dockerfile指令未使用它。
WORKDIR指令可在Dockerfile中屢次使用。若是提供了相對路徑,則它將相對於上一個WORKDIR指令的路徑。例如:
WORKDIR /a WORKDIR b WORKDIR c RUN pwd
該Dockerfile中最後一個pwd命令的輸出爲 /a/b/c 。
WORKDIR指令能夠解析之前使用ENV設置的環境變量。您只能使用在Dockerfile中顯式設置的環境變量。例如:
ENV DIRPATH /path WORKDIR $DIRPATH/$DIRNAME RUN pwd
該Dockerfile中最後一個pwd命令的輸出爲 /path/$DIRNAME
RUN指令將在當前映像頂部的新層中執行任何命令,並提交結果。生成的提交映像將用於Dockerfile中的下一步。
RUN <command> (shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows) RUN ["executable", "param1", "param2"] (exec form)
分層運行RUN指令並生成提交符合Docker的核心概念,在Docker上,提交很便宜,而且能夠從映像歷史記錄的任何位置建立容器,就像源代碼控制同樣。
在shell形式中,可使用(反斜槓)將一條RUN指令繼續到下一行。
RUN /bin/bash -c 'source $HOME/.bashrc; \ echo $HOME'
Together they are equivalent to this single line:
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
注意:要使用 ‘/bin/sh’ 之外的其餘 shell,請使用 exec 形式傳入所需的 shell。例如,RUN ["/bin/bash", "-c", "echo hello"]
注意:不一樣與shell形式,exec形式不會調用命令shell。 這意味着正常的外殼處理不會發生。 例如,RUN ["echo", "$HOME"] 不會在$HOME上進行變量替換。 若是要進行shell處理,則可使用shell形式或直接執行shell,例如:RUN ["sh", "-c", "echo $HOME"] 。
注意:在JSON格式中,必須轉義反斜槓。 在Windows中,反斜槓是路徑分隔符,這一點尤爲重要。 因爲無效的JSON,如下行將被視爲shell形式,並以意外的方式失敗:RUN ["c:windowssystem32tasklist.exe"] 此示例的正確語法是:RUN ["c:\windows\system32\tasklist.exe"]
在下一個構建時,RUN指令的緩存不會自動失效。 諸如RUN apt-get dist-upgrade -y之類的指令的緩存將在下一次構建中重用。 可使用--no-cache標誌使RUN指令的緩存無效,例如docker build --no-cache。
EXPOSE指令通知Docker運行時容器在指定的網絡端口上進行偵聽。您能夠指定端口是偵聽TCP仍是UDP,若是未指定協議,則默認值爲TCP。
EXPOSE指令是聲明運行時容器提供服務端口,這只是一個聲明,在運行時並不會由於這個聲明應用就會開啓這個端口的服務。在 Dockerfile 中寫入這樣的聲明有兩個好處,一個是幫助鏡像使用者理解這個鏡像服務的守護端口,以方便配置映射;另外一個用處則是在運行時使用隨機端口映射時,也就是 docker run -P 時,會自動隨機映射 EXPOSE 的端口。
要將 EXPOSE 和在運行時使用 -p <宿主端口>:<容器端口> 區分開來。-p,是映射宿主端口和容器端口,換句話說,就是將容器的對應端口服務公開給外界訪問,而 EXPOSE 僅僅是聲明容器打算使用什麼端口而已,並不會自動在宿主進行端口映射。格式爲 EXPOSE <端口1> [<端口2>...]。
默認狀況下,EXPOSE假定使用TCP。您還能夠指定UDP:
EXPOSE 80/udp
要同時在TCP和UDP上公開,請包括如下兩行:
EXPOSE 80/tcp EXPOSE 80/udp
不管EXPOSE設置如何,均可以在運行時使用-p標誌覆蓋它們。例如
docker run -p 80:80/tcp -p 80:80/udp ...
docker network命令支持建立用於容器之間通訊的網絡,而無需暴露或發佈特定端口,由於鏈接到網絡的容器能夠經過任何端口相互通訊。有關詳細信息,請參閱此功能的概述。
用於指定默認的容器主進程的啓動命令。Dockerfile中只能有一條CMD指令。若是您列出多個CMD,則只有最後一個CMD纔會生效。
CMD的主要目的是爲執行中的容器提供默認值。這些默認值能夠包含一個可執行文件,也能夠忽略該可執行文件,在這種狀況下,您還必須指定ENTRYPOINT指令。
注意:若是使用CMD爲ENTRYPOINT指令提供默認參數,則CMD和ENTRYPOINT指令均應使用JSON數組格式指定。
CMD指令具備三種形式:
CMD ["executable","param1","param2"] (exec form, this is the preferred form) CMD ["param1","param2"] (在指定了 ENTRYPOINT 指令後,用 CMD 指定具體的參數。) CMD command param1 param2 (shell form)
If you use the shell form of the CMD, then the <command> will execute in /bin/sh -c:
FROM ubuntu CMD echo "This is a test." | wc -
若是要在沒有shell的狀況下運行<command>,則必須將命令表示爲JSON數組,並提供可執行文件的完整路徑。此數組形式是CMD的首選格式。任何其餘參數必須在數組中分別表示爲字符串:
FROM ubuntu CMD ["/usr/bin/wc","--help"]
注意,指定了CMD命令之後,docker container run命令就不能附加命令了(好比 /bin/bash),不然它會覆蓋CMD命令。
RUN命令與CMD命令的區別在哪裏?
簡單說,RUN命令在 image 文件的構建階段執行,執行結果都會打包進入 image 文件;CMD命令則是在容器啓動後執行。另外,一個 Dockerfile 能夠包含多個RUN命令,可是隻能有一個CMD命令。
Volume,一般翻譯爲數據卷,用於保存持久化數據。當咱們將數據庫例如MySQL運行在Docker容器中時,通常將數據經過Docker Volume保存在主機上,這樣即便刪除MySQL容器,數據依然保存在主機上,有效保證了數據的安全性。
VOLUME指令建立具備指定名稱的掛載點,並將其標記爲保存來自本地主機或其餘容器的外部安裝的卷。該值能夠是JSON數組,VOLUME ["/var/log/"], 或具備多個參數的純字符串,例如VOLUME /var/log or VOLUME /var/log /var/db。
咱們知道,鏡像的每一層都是 ReadOnly 只讀的。只有在咱們運行容器的時候纔會建立讀寫層。文件系統的隔離使得:
docker 爲咱們提供了三種不一樣的方式將數據掛載到容器中:volume、bind mount、tmpfs。
volume 方式是 docker 中數據持久化的最佳方式。
volume 在容器中止或刪除的時候會繼續存在,如需刪除須要顯示聲明。
$ docker rm -v <container_id> $ docker volume rm <volume_name>
格式:
VOLUME ["<路徑1>", "<路徑2>"...] VOLUME <路徑>
能夠經過如下兩種方式建立 VOLUME:
這兩種方式有區別嗎?
根據官方文檔,Dockerfile生成目標鏡像的過程就是不斷 docker run + docker commit 的過程,當 Dockerfile 執行到 VOLUME /some/dir(這裏爲/var/lib/mysql)這一行時,輸出:
Step 6 : VOLUME /var/lib/mysql ---> Running in 0c842ec90849 ---> 214e3dccd0f2
在這一步,docker生成了臨時容器0c842ec90849,而後commit容器獲得鏡像214e3dccd0f2。所以 VOLUME /var/lib/mysql 是經過 docker run -v /var/lib/mysql,即第二種方式來實現的,隨後因爲容器的提交,該配置被保存到了鏡像214e3dccd0f2中,經過inspcet能夠查看到:
"Volumes": { "/var/lib/mysql": {}, }
使用docker inspect命令,能夠查看Docker容器的詳細信息:
docker inspect --format='{{json .Mounts}}' test | python -m json.tool "Mounts": [ { "Name": "8827c361d103c1272907da0b82268310415f8b075b67854f27dbca0b59a31a1a", "Source": "/mnt/sda1/var/lib/docker/volumes/8827c361d103c1272907da0b82268310415f8b075b67854f27dbca0b59a31a1a/data", "Destination": "/var/lib/mysql", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ]
Source表示主機上的目錄,Destination爲容器中的目錄。
因爲沒有指定掛載到的宿主機目錄,所以會默認掛載到宿主機的 /var/lib/docker/volumes 下的一個隨機名稱的目錄下,在這爲 /mnt/sda1/var/lib/docker/volumes/8827c361d103c1272907da0b82268310415f8b075b67854f27dbca0b59a31a1a/data 。所以Dockerfile中使用VOLUME指令掛載目錄和docker run時經過-v參數指定掛載目錄的區別在於,run的-v能夠指定掛載到宿主機的哪一個目錄,而Dockerfile的VOLUME不能,其掛載目錄由docker隨機生成。
若指定了宿主機目錄,好比:
docker run --name mysql -v ~/volume/mysql/data:/var/lib/mysql -d mysql:5.7
那麼inspect以下:
"Mounts": [ { "Source": "/Users/weizi/volume/mysql/data", "Destination": "/var/lib/mysql", "Mode": "", "RW": true, "Propagation": "rprivate" } ]
這裏將 /var/lib/mysql 掛載到宿主機的 /Users/weizi/volume/mysql/data 目錄下,而再也不是默認的 /var/lib/docker/volumes 目錄。這樣作有什麼好處呢?咱們知道,將該目錄掛載到宿主機,可使數據在容器被移除時得以保留,而不會隨着容器go die。下次新建mysql容器,只需一樣掛載到 /Users/weizi/volume/mysql/data,便可複用原有數據。
在宿主機 /Users/weizi/volume/mysql/data 目錄中新建 hello.txt 文件,在容器/var/lib/mysql 目錄中可見。
在容器 /var/lib/mysql 目錄中新建 world.txt 文件,在宿主機 /Users/weizi/volume/mysql/data 目錄中可見。
經過VOLUME,咱們得以繞過docker的Union File System,從而直接對宿主機的目錄進行直接讀寫,實現了容器內數據的持久化和共享化。
關於Dockerfile中的卷,請記住如下幾點。
基於Windows的容器上的卷:使用基於Windows的容器時,容器內的卷的目的地必須是如下之一:
完成 Dockerfile 文檔以後,接下來咱們能夠基於 Dockerfile 文檔生成鏡像文件了。
docker build -t passport-scratch:1.0.1 -f Dockerfile .
以上,若是運行成功,就能夠看到新生成的 image 文件了。
$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE passport-busybox 1.0.1 1028fbd88847 10 minutes ago 36.3MB passport-scratch 1.0.1 aa407fee8d95 42 minutes ago 35.1MB passport-multi-stage 1.0.9 dd8a070d96e9 2 days ago 59.4MB passport-busybox 1.0.0 d56a21693694 3 days ago 36.3MB <none> <none> 492f4b83ea2d 16 minutes ago 34.9MB nginx v1 75b671fe9af3 9 days ago 126MB busybox latest 020584afccce 2 weeks ago 1.22MB nginx latest 540a289bab6c 3 weeks ago 126MB alpine latest 965ea09ff2eb 3 weeks ago 5.55MB ubuntu 19.10 09604a62a001 4 weeks ago 72.9MB kong latest 03f9bc1cd4f7 2 months ago 130MB postgres 9.6 f5548544c480 3 months ago 230MB pantsel/konga latest dc0af5db6ce9 7 months ago 389MB pgbi/kong-dashboard latest f9e2977207e3 8 months ago 96.4MB golang 1.10 6fd1f7edb6ab 9 months ago 760MB golang 1.10-alpine 7b53e4a31d21 9 months ago 259MB golang 1.10.3-alpine cace225819dc 15 months ago 259MB golang 1.10.3 d0e7a411e3da 16 months ago 794MB soyking/e3w latest a123f3eeaad2 23 months ago 24.2MB soyking/etcd-goreman 3.2.7 4c0139e55ed5 2 years ago 121MB
上面代碼中,-t參數用來指定 image 文件的名字(名字中不能報考下劃線_),後面還能夠用冒號指定標籤。若是不指定,默認的標籤就是latest。最後的那個點表示 Dockerfile 文件所在的路徑,上例是當前路徑,因此是一個點。
docker build 命令構建鏡像,其實並不是在本地構建,而是在服務端,也就是 Docker 引擎中構建的。那麼在這種客戶端/服務端的架構中,如何才能讓服務端得到本地文件呢?
這就引入了上下文的概念。當構建的時候,用戶會指定構建鏡像上下文的路徑(也就是上面命令中最後的那個圓點 "."),docker build 命令得知這個路徑後,會將路徑下的全部內容打包,而後上傳給 Docker 引擎。這樣 Docker 引擎收到這個上下文包後,展開就會得到構建鏡像所需的一切文件。
Ok, 接下來咱們來生成容器。docker container run 命令會基於 image 文件生成容器。
$ docker run -p 80:8080 -it passport-scratch:1.0.1 no such file or directory
從上面能夠看出,運行容器時報錯了。這是爲何呢?
Go 二進制文件正在其運行的操做系統上尋找一些庫。咱們編譯了應用,但它仍動態連接到須要運行的庫(即,它綁定到的全部C庫)。不幸的是,scratch 是空的,所以沒有庫。咱們要作的是修改構建腳本,以使用全部內置庫靜態編譯咱們的應用程序。
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o output/bin/go_service_passport ./app OR CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o output/bin/go_service_passport ./app
上面的命令,咱們禁用了cgo,它爲咱們提供了靜態二進制文件。咱們還將操做系統設置爲Linux(以防有人在Mac或Windows上構建),-a標誌意味着能夠重建咱們正在使用的全部軟件包,這意味着全部導入將在禁用cgo的狀況下進行重建。
從新生成二進制文件後,讓咱們再次試一下:
# 後臺運行 $ docker container run -it -p 80:8080 -d passport-scratch:1.0.1 OR # 前臺運行並刪除 $ docker container run --rm -p 8080:8080 -it passport-scratch:1.0.1 2019-11-15T17:25:33.747+0800 DEBUG setting/etcd.go:35 setting.etcd: Backend config {"backend": "etcdv3", "machines": ["127.0.0.1:2379"], "keyspace": "", "traceId": ""}
這時發現仍然有問題,容器內的主程序是能夠運行了,不過很快就退出了。什麼緣由呢?
這是由於 passport 程序內部有鏈接 etcd,consul,mysql 等服務,而這些基礎服務在宿主機器上默認監聽地址是127.0.0.1。而容器內也確實存在127.0.0.1/localhost地址。不過這和宿主機上的127.0.0.1是不同的,因此容器內的程序就沒法訪問宿主機上的127.0.0.1/localhost。解決方法是把宿主機器上的 etcd, consul 等服務的監聽地址修改成0.0.0.0 。如:
#consul $ nohup /usr/local/opt/consul/bin/consul agent -dev -client 0.0.0.0 $ lsof -nP -iTCP -sTCP:LISTEN | grep consul consul 79872 will 5u IPv4 0x9c21088b2c0aa1b 0t0 TCP 127.0.0.1:8300 (LISTEN) consul 79872 will 6u IPv4 0x9c210888fd5863b 0t0 TCP 127.0.0.1:8302 (LISTEN) consul 79872 will 8u IPv4 0x9c21088b2c1137b 0t0 TCP 127.0.0.1:8301 (LISTEN) consul 79872 will 11u IPv6 0x9c21088832ff0d3 0t0 TCP *:8600 (LISTEN) consul 79872 will 12u IPv6 0x9c21088832fd413 0t0 TCP *:8500 (LISTEN) #etcd $ nohup ./etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379 $ lsof -nP -iTCP -sTCP:LISTEN | grep etcd etcd 80114 will 4u IPv4 0x9c21088b0d6463b 0t0 TCP 127.0.0.1:2380 (LISTEN) etcd 80114 will 6u IPv6 0x9c21088832ff693 0t0 TCP *:2379 (LISTEN)
好,讓咱們再重啓下容器並加上環境變量
# 後臺運行 $ docker container run -it -p 80:8080 -d -e ETCD_ADDR=172.16.60.88:2379 -e CONSUL_ADDR=172.16.60.88:8500 passport-scratch:1.0.1 OR # 前臺運行並刪除 $ docker container run --rm -p 8080:8080 -it -e ETCD_ADDR=172.16.60.88:2379 -e CONSUL_ADDR=172.16.60.88:8500 passport-scratch:1.0.1 2019-11-15T17:25:33.747+0800 DEBUG setting/etcd.go:35 setting.etcd: Backend config {"backend": "etcdv3", "machines": ["127.0.0.1:2379"], "keyspace": "", "traceId": ""} ###如下是啓動信息 2019-11-15T22:46:12.278+0800 DEBUG setting/etcd.go:35 setting.etcd: Backend config {"backend": "etcdv3", "machines": ["10.10.1.29:2379"], "keyspace": "", "traceId": ""} 2019-11-15T22:46:12.290+0800 DEBUG setting/etcd.go:65 setting.etcd: Retrieved mysql-key-val from etcd store {"key": "root/config/common/database/mysql/passport", "config": {"master":{"dsn":"","user":"root","pass":"GL@c*Nm#dkaLH!FNe","host":"rm-j6cli54dhwo5ski2quo.mysql.rds.aliyuncs.com","port":3306,"dbname":"peduli","max_open":100,"max_idle":10},"slave":{"dsn":"","user":"root","pass":"GL@c*Nm#dkaLH!FNe","host":"rm-j6cli54dhwo5ski2quo.mysql.rds.aliyuncs.com","port":3306,"dbname":"peduli","max_open":100,"max_idle":10}}, "traceId": ""} DEBU[0001] new passport mysql store MasterDB="&{<nil> <nil> 0 0xc42022ac80 false 2 {0xc42034a280} <nil> map[] 0xc4200e05a0 0x16c7aa0 0xc4201e39a0 false}" SlaveDB="&{<nil> <nil> 0 0xc42022ae60 false 2 {0xc42034a280} <nil> map[] 0xc4200e06c0 0x16c7aa0 0xc4201e3b60 false}" 2019-11-15T22:46:13.888+0800 DEBUG setting/etcd.go:35 setting.etcd: Backend config {"backend": "etcdv3", "machines": ["10.10.1.29:2379"], "keyspace": "", "traceId": ""} 2019-11-15T22:46:13.894+0800 DEBUG setting/etcd.go:102 setting.etcd: Retrieved redis-key-val from etcd store {"key": "root/config/common/database/redis", "config": {"master":{"addr":"127.0.0.1:6379","password":"","db":1},"slave":{"addr":"127.0.0.1:6379","password":"","db":1}}, "traceId": ""} 2019-11-15T22:46:13.895+0800 DEBUG setting/etcd.go:35 setting.etcd: Backend config {"backend": "etcdv3", "machines": ["10.10.1.29:2379"], "keyspace": "", "traceId": ""} 2019-11-15T22:46:13.902+0800 DEBUG setting/etcd.go:102 setting.etcd: Retrieved redis-key-val from etcd store {"key": "root/config/common/database/redis", "config": {"master":{"addr":"127.0.0.1:6379","password":"","db":1},"slave":{"addr":"127.0.0.1:6379","password":"","db":1}}, "traceId": ""} 2019-11-15 22:46:13.905270 I | Initializing logging reporter INFO[0001] new command Command="&{<nil> <nil>}" 2019-11-15T22:46:13.907+0800 DEBUG setting/etcd.go:35 setting.etcd: Backend config {"backend": "etcdv3", "machines": ["10.10.1.29:2379"], "keyspace": "", "traceId": ""} 2019-11-15T22:46:13.916+0800 DEBUG setting/etcd.go:65 setting.etcd: Retrieved mysql-key-val from etcd store {"key": "root/config/common/database/mysql/passport", "config": {"master":{"dsn":"","user":"root","pass":"GL@c*Nm#dkaLH!FNe","host":"rm-j6cli54dhwo5ski2quo.mysql.rds.aliyuncs.com","port":3306,"dbname":"peduli","max_open":100,"max_idle":10},"slave":{"dsn":"","user":"root","pass":"GL@c*Nm#dkaLH!FNe","host":"rm-j6cli54dhwo5ski2quo.mysql.rds.aliyuncs.com","port":3306,"dbname":"peduli","max_open":100,"max_idle":10}}, "traceId": ""} DEBU[0003] new passport mysql store MasterDB="&{<nil> <nil> 0 0xc4200ba6e0 false 2 {0xc42034a280} <nil> map[] 0xc4200e0a20 0x16c7aa0 0xc42019bee0 false}" SlaveDB="&{<nil> <nil> 0 0xc4201c3ea0 false 2 {0xc42034a280} <nil> map[] 0xc420376480 0x16c7aa0 0xc4201e2780 false}" DEBU[0003] new store MySQL="&{0xc4200e0a20 0xc420376480 0xc4200831e0}" config="&{dev [10.10.1.29:2379] 0 0 }" 2019-11-15T22:46:15.736+0800 DEBUG setting/etcd.go:35 setting.etcd: Backend config {"backend": "etcdv3", "machines": ["10.10.1.29:2379"], "keyspace": "", "traceId": ""} 2019-11-15T22:46:15.742+0800 DEBUG setting/etcd.go:83 setting.etcd: Retrieved service-key-val from etcd store {"key": "root/config/custom/go_service_passport", "config": {"Runmode":"","EtcdEndpoints":null,"AppConfigPath":"","ServiceName":"","ServiceIP":"","ServiceHttpPort":0,"ServiceRpcPort":0,"log_level":"debug","log_path":"","domain_www":"https://www.pedulisehat.id","domain_api":"","domain_passport":"https://passport-qa.pedulisehat.id","domain_project":"https://project-qa.pedulisehat.id","domain_trade":"https://trade-qa.pedulisehat.id","domain_static_avatar":"https://static-qa.pedulisehat.id/img/avatar","url_share_project":"","url_ico":"","host_passport":"","host_project":"","host_trade":"","url_share":"","domain_gtry":""}, "traceId": ""} DEBU[0003] setting.NewConfig Config="&{dev [10.10.1.29:2379] go_service_passport 0.0.0.0 8080 9080 debug https://www.pedulisehat.id https://passport-qa.pedulisehat.id https://project-qa.pedulisehat.id https://trade-qa.pedulisehat.id https://static-qa.pedulisehat.id/img/avatar }" INFO[0003] command run ... Command="&{<nil> 0xc4201e21c0}" 2019-11-15 22:46:15.744629 I | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) 2019-11-15 22:46:15.745561 I | RPC Server has been started up. 172.17.0.2:9080
能夠發現咱們的 passport 服務能夠正常啓動了,查看下容器的運行狀態:
$ docker container ls --all CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b58ee39088dd passport-multi-stage:1.0.9 "./go_service_passpo…" 5 seconds ago Up 4 seconds 9080/tcp, 0.0.0.0:80->8080/tcp recursing_poitras
參數說明:
-p 參數: 容器的 8080 端口映射到本機的 80 端口。
-it 參數: 容器的 Shell 映射到當前的 Shell,而後你在本機窗口輸入的命令,就會傳入容器。其中,-t 選項讓Docker分配一個僞終端(pseudo-tty)並綁定到容器的標準輸入上, -i 則讓容器的標準輸入保持打開。
passport-scratch:1.0.1: image 文件的名字(若是有標籤,還須要提供標籤,默認是 latest 標籤)。
當利用 docker run 來建立容器時,Docker 在後臺運行的標準操做包括:
到此,完整的docker容器製做流程講完了!
容器運行成功後,就確認了 image 文件的有效性。這時,咱們就能夠考慮把 image 文件分享到網上,讓其餘人使用。image 文件製做完成後,能夠上傳到網上的倉庫。
首先,去 Docker 的官方倉庫 Docker Hub 註冊一個帳戶,這是最重要、最經常使用的 image 倉庫。
$ docker login Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one. Username: isgiker Password: Login Succeeded
接着,爲本地的 image 標註用戶名和版本。
$ docker image tag [imageName] [username]/[repository]:[tag] # 實例 $ docker image tag passport-multi-stage:1.0.9 isgiker/passport-multi-stage:1.0.9
最後,發佈 image 文件。
$ docker image push isgiker/passport-multi-stage:1.0.9 The push refers to repository [docker.io/isgiker/passport-multi-stage] e22072d3470d: Pushed 9136612a4372: Pushed dac53910d311: Pushed 77cae8ab23bf: Mounted from library/alpine 1.0.9: digest: sha256:b5e9f0db2bd3e9ba684c8c359b087aa097adbb6a7426732b6d9246ca1b3dd6dc size: 1158
能夠經過 docker search 命令來查找官方倉庫中的鏡像,
$ docker search keywords[username/image name]
docker image COMMAND
Child commands
Command | Description |
---|---|
docker image build | Build an image from a Dockerfile |
docker image history | Show the history of an image |
docker image import | Import the contents from a tarball to create a filesystem image |
docker image inspect | Display detailed information on one or more images |
docker image load | Load an image from a tar archive or STDIN |
docker image ls | List images |
docker image prune | Remove unused images |
docker image pull | Pull an image or a repository from a registry |
docker image push | Push an image or a repository to a registry |
docker image rm | Remove one or more images |
docker image save | Save one or more images to a tar archive (streamed to STDOUT by default) |
docker image tag | Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE |
docker container COMMAND
Child commands
Command | Description |
---|---|
docker container attach | Attach local standard input, output, and error streams to a running container |
docker container commit | Create a new image from a container’s changes |
docker container cp | Copy files/folders between a container and the local filesystem |
docker container create | Create a new container |
docker container diff | Inspect changes to files or directories on a container’s filesystem |
docker container exec | Run a command in a running container |
docker container export | Export a container’s filesystem as a tar archive |
docker container inspect | Display detailed information on one or more containers |
docker container kill | Kill one or more running containers |
docker container logs | Fetch the logs of a container |
docker container ls | List containers |
docker container pause | Pause all processes within one or more containers |
docker container port | List port mappings or a specific mapping for the container |
docker container prune | Remove all stopped containers |
docker container rename | Rename a container |
docker container restart | Restart one or more containers |
docker container rm | Remove one or more containers |
docker container run | Run a command in a new container |
docker container start | Start one or more stopped containers |
docker container stats | Display a live stream of container(s) resource usage statistics |
docker container stop | Stop one or more running containers |
docker container top | Display the running processes of a container |
docker container unpause | Unpause all processes within one or more containers |
docker container update | Update configuration of one or more containers |
docker container wait | Block until one or more containers stop, then print their exit codes |
Child commands
Command | Description |
---|---|
docker attach | Attach local standard input, output, and error streams to a running container |
docker build | Build an image from a Dockerfile |
docker builder | Manage builds |
docker checkpoint | Manage checkpoints |
docker commit | Create a new image from a container’s changes |
docker config | Manage Docker configs |
docker container | Manage containers |
docker context | Manage contexts |
docker cp | Copy files/folders between a container and the local filesystem |
docker create | Create a new container |
docker deploy | Deploy a new stack or update an existing stack |
docker diff | Inspect changes to files or directories on a container’s filesystem |
docker engine | Manage the docker engine |
docker events | Get real time events from the server |
docker exec | Run a command in a running container |
docker export | Export a container’s filesystem as a tar archive |
docker history | Show the history of an image |
docker image | Manage images |
docker images | List images |
docker import | Import the contents from a tarball to create a filesystem image |
docker info | Display system-wide information |
docker inspect | Return low-level information on Docker objects |
docker kill | Kill one or more running containers |
docker load | Load an image from a tar archive or STDIN |
docker login | Log in to a Docker registry |
docker logout | Log out from a Docker registry |
docker logs | Fetch the logs of a container |
docker manifest | Manage Docker image manifests and manifest lists |
docker network | Manage networks |
docker node | Manage Swarm nodes |
docker pause | Pause all processes within one or more containers |
docker plugin | Manage plugins |
docker port | List port mappings or a specific mapping for the container |
docker ps | List containers |
docker pull | Pull an image or a repository from a registry |
docker push | Push an image or a repository to a registry |
docker rename | Rename a container |
docker restart | Restart one or more containers |
docker rm | Remove one or more containers |
docker rmi | Remove one or more images |
docker run | Run a command in a new container |
docker save | Save one or more images to a tar archive (streamed to STDOUT by default) |
docker search | Search the Docker Hub for images |
docker secret | Manage Docker secrets |
docker service | Manage services |
docker stack | Manage Docker stacks |
docker start | Start one or more stopped containers |
docker stats | Display a live stream of container(s) resource usage statistics |
docker stop | Stop one or more running containers |
docker swarm | Manage Swarm |
docker system | Manage Docker |
docker tag | Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE |
docker top | Display the running processes of a container |
docker trust | Manage trust on Docker images |
docker unpause | Unpause all processes within one or more containers |
docker update | Update configuration of one or more containers |
docker version | Show the Docker version information |
docker volume | Manage volumes |
docker wait | Block until one or more containers stop, then print their exit codes |
Building Docker Containers for Go Applications
Deploying a containerized Go app on Kubernetes
Building Minimal Docker Containers for Go Applications
Docker ARG, ENV and .env - a Complete Guide
How To Pass Environment Info During Docker Builds
跟我一塊兒學Docker——Volume