Docker 最初是 dotCloud 公司創始人 Solomon Hykes 在法國期間發起的一個公司內部項目,它是基於 dotCloud 公司多年雲服務技術的一次革新,並於 2013 年 3 月以 Apache 2.0 受權協議開源。css
Docker 是一個開源的應用容器引擎,使用 Go 語言 進行開發實現,它不一樣於與 KVM 和 Xen,docker 基於 Linux 內核的 cgroup,namespace,以及 AUFS 類的 Union FS 等技術,對進程進行封裝隔離,屬於 操做系統層面的虛擬化技術。html
Docker 容器內的應用進程直接運行於宿主的內核,容器內沒有本身的內核,並且也沒有進行硬件虛擬。因此它很是的輕量。使用 docker 能夠解決咱們的軟件開發中的依賴和開發環境統一等問題。前端
docker 分爲兩個版本,docker-ce(社區版) 和 docker-ee(企業版),docker-ce 是免費的支持週期 7 個月,ee 須要付費,支持週期 24 個月。node
安裝 docker 能夠直接去查看官網安裝文檔頁面。react
國內從 Docker Hub 拉取鏡像會很慢,咱們能夠更換鏡像源,對於 windows 和 mac 能夠直接去設置 daemon 中設置 registry mirrors。linux
linux 能夠修改 /etc/docker/daemon.json
文件。nginx
{
"registry-mirrors": [
"https://dockerhub.azk8s.cn",
"https://reg-mirror.qiniu.com"
]
}
複製代碼
而後重啓就能夠了。git
docker 中有三個主要概念,鏡像(Image),容器(Container)和倉庫(repository)。golang
Docker 鏡像是一個特殊的文件系統,除了提供容器運行時所需的程序、庫、資源、配置等文件外,還包含了一些爲運行時準備的一些配置參數(如匿名卷、環境變量、用戶等)。鏡像不包含任何動態數據,其內容在構建以後也不會被改變。web
鏡像就好像是 類
而容器就是 實例
。
容器的實質是進程,但與直接在宿主執行的進程不一樣,容器進程運行於屬於本身的獨立的 命名空間。所以容器能夠擁有本身的 root 文件系統、本身的網絡配置、本身的進程空間,甚至本身的用戶 ID 空間。容器內的進程是運行在一個隔離的環境裏,使用起來,就好像是在一個獨立於宿主的系統下操做同樣。
好比一個運行 nginx 的容器
容器運行時,是以鏡像爲基礎層,在其上建立一個當前容器的存儲層,咱們能夠稱這個爲容器運行時讀寫而準備的存儲層爲 容器存儲層。當容器消亡時,容器存儲層也隨之消亡,上面的存儲的信息都會被刪除。
若是想數據不隨着容器退出而被刪除,可使用數據卷或綁定宿主目錄,在這些位置的讀寫會跳過容器存儲層,直接對宿主(或網絡存儲)發生讀寫,其性能和穩定性更高。容器消亡,數據卷不會消亡。
鏡像構建完成後,就能夠發佈到遠程倉庫中去,它來集中的存儲、分發鏡像,這樣就能夠被他人下載使用了,Docker Registry 就是這樣的服務
一個 Docker Registry 中能夠包含多個 倉庫(Repository)每一個倉庫能夠包含多個 標籤(Tag);每一個標籤對應一個鏡像。
鏡像的命名使用 命名空間(用戶名)/倉庫名:標籤
的形式,好比 jwilder/nginx-proxy:latest
。若是咱們直接使用 jwilder/nginx-proxy
也是表明 jwilder/nginx-proxy:latest
。由於不給出標籤,將以 latest
做爲默認標籤。
官方鏡像沒有命名空間這一段,咱們能夠直接使用 centos
這樣的鏡像名。
最常使用的 Registry 公開服務是官方的 Docker Hub,這也是默認的 Registry。咱們能夠去上面查看鏡像說明,評論等信息。
咱們安裝的 Docker 分爲兩個部分 Docker Client 和 Docker Server。咱們經過 Client 發送命令到 Server,由 Server 來建立鏡像,運行容器等工做。
docker 是基於 Linux 內核的 cgroup,namespace。
因爲 windows 和 mac 上面沒有上述的技術,因此 windows 和 mac 上的 docker 實際上是運行在一個 linux 虛擬機中的。
由於鏡像包含操做系統完整的 root 文件系統,其體積每每是龐大的,所以在 Docker 設計時,就充分利用 Union FS 的技術,將其設計爲分層存儲的架構。一個鏡像實際上是由多層文件系統聯合組成。
鏡像構建時,會一層層構建,前一層是後一層的基礎。每一層構建完就不會再發生改變,後一層上的任何改變只發生在本身這一層。
刪除前一層文件的操做,實際不是真的刪除前一層的文件,而是僅在當前層標記爲該文件已刪除。在最終容器運行的時候,雖然不會看到這個文件,可是實際上該文件會一直跟隨鏡像。
當咱們要在本機運行一個容器時,好比使用 nginx 鏡像啓一個容器,咱們能夠執行docker run nginx
命令,docker 會首先查看本地鏡像緩存中是否存在該鏡像,若是沒有就從遠程倉庫下載 nginx:latest
鏡像下來,保存到本地的鏡像緩存中,而後經過該鏡像啓動運行一個容器。
鏡像就像是一個特殊文件系統,它想一個文件系統快照,當啓動運行一個容器時, docker 首先會在宿主機的硬盤上劃分一片區域,它只能被該容器訪問, 而後鏡像上的文件快照放入這一片區域中。而後運行鏡像的啓動命令。
安裝好 docker 後,咱們可使用
docker version
複製代碼
查看版本信息。
docker info
複製代碼
查看全系統的信息,好比配置和當前狀態信息。
docker --help
複製代碼
查看幫助信息。
docker 有兩種命令形式。
docker <command> (options) # 老命令形式
docker <command> <sub-command> (options) # 新命令形式
複製代碼
好比 docker ps
等同於 docker container ls
。
docker search 關鍵字
# 能夠經過關鍵字搜索鏡像。
複製代碼
docker pull 鏡像
# 能夠把遠程的鏡像拉取到本地。
複製代碼
docker images
# 能夠列出本地的鏡像,列表中的鏡像體積總和並不是是全部鏡像實際硬盤消耗。
# 由於 Docker 鏡像是多層存儲結構,而且能夠繼承、複用。
複製代碼
docker system df
# 能夠查看鏡像,容器和數據卷佔用內存信息
複製代碼
鏡像列表中有可能看見一個特殊的鏡像,倉庫名和標籤,均爲 <none>
。這是由於因爲新舊鏡像同名,舊鏡像名稱被取消,從而出現倉庫名、標籤均爲 <none>
的鏡像。
docker images -a
複製代碼
能夠顯示包括中間層鏡像在內的全部鏡像,多個頂層鏡像可能依賴同一個中間層鏡像,因此中間層鏡像不能夠隨便刪除,若是一箇中間層鏡像沒有被依賴,那麼它就會被自動刪除。
docker rmi 鏡像
複製代碼
若是有基於這個鏡像啓動的容器,會致使刪除失敗,須要使用 -f
參數強制刪除。
刪除鏡像分爲 Untagged
和 Deleted
兩類。
一個鏡像能夠有多個標籤,咱們刪除一個鏡像其實是取消標籤,當該鏡像全部的標籤都被取消了,那麼就會觸發 Deleted
行爲。
容器是獨立運行的一個或一組應用,以及它們的運行態環境。
docker create 鏡像
# 經過鏡像建立一個容器。
# `--name 名稱` 能夠給建立的容器一個名稱,這樣就不會是隨機名稱
複製代碼
docker start 容器 [...]
# 運行一個或多個容器。
複製代碼
docker run 鏡像 [COMMAND]
# 經過鏡像建立並啓動一個容器,若是鏡像不存在,會自動去遠端倉庫拉去鏡像,
# 它就至關於`docker pull` `docker create` `docker start` 這三個命令。
複製代碼
常見的參數
--name 名稱
設置容器名稱-d
在背景以守護進程運行--rm
當容器推出時自動刪除-p 機器端口:容器端口
機器端口映射到容器端口-e 變量名=變量值
設置環境變量-i
保持開啓 STDIN,讓容器能夠接受到咱們鍵盤發出的命令-t
分配一個僞終端
docker run --name webserver --rm -d -p 80:80 nginx
# 啓動 nginx 服務器,這時候去瀏覽器輸入 127.0.0.1 能夠看見默認的 nginx 頁面
複製代碼
docker run -it nginx sh
# nginx 容器啓動命令爲 sh,而不是默認命令,它重寫了默認執行的命令。
複製代碼
docker run -it --name hi busybox echo hi
# 它會打印 hi 而後結束
# 咱們可使用 docker start hi 來從新運行這個容器,它還會打印 hi
# 然而 docker start 命令不能夠重寫容器的啓動命令
複製代碼
docker ps
# 打印出容器列表
# `-a` 顯示全部容器列表,即便不在運行
複製代碼
docker stats
# 顯示容器實時的資源使用狀況
# `-a` 參數 讓輸出顯示到屏幕上,而不是隻是返回一個 id
複製代碼
docker top 容器
# 顯示容器運行進程
複製代碼
docker logs 容器
# 容器守護態運行時,沒有輸出信息,能夠經過這個命令打印容器的輸出信息。
# `-f` 參數爲不退出,一直等待最新的日誌輸出。
複製代碼
docker stop 容器 [...]
# 中止一個或多個容器
# 它發送一個 SIGTERM 信號個容器,容器中的那個進程收到信號後
# 可能會執行一些其餘操做才中止,因此有時須要等一會才中止容器
複製代碼
docker kill 容器
# 發送一個 SIGKILL 信號給容器
# 它的意思是讓容器中的進程立刻關閉,不要作一些其餘的事
# 若是咱們使用 stop 命令,容器 10 秒中沒有響應,docker 會自動執行 kill 命令
複製代碼
docker restart 容器 [...]
# 重啓一個或多個容器
複製代碼
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
# 在運行的容器中,執行一個命令
複製代碼
若是咱們進入一個在運行的容器,好比上面 webserver 容器。咱們能夠執行。
docker exec -it webserver sh
複製代碼
docker rm 容器
# 刪除容器
# 若是是在運行的容器能夠加 `-f` 參數,發送 SIGKILL 信號給容器
複製代碼
docker container prune
# 清理全部處於終止狀態的容器
複製代碼
docker system prune
# 當咱們 docker 使用久了,可使用這個命令來清理一下空間
複製代碼
製做鏡像的一種簡單的方法是直接經過一個容器生成本身鏡像。
咱們首先能夠啓動一個容器
docker run --name webserver -d -p 80:80 nginx
複製代碼
而後進入容器中,而後更改容器中 nginx 默認的 index 頁面
docker exec -it webserver bash
/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
/# exit
複製代碼
退出後咱們用 diff
命令,查看變更過的文件
$ docker diff webserver
C /root
A /root/.bash_history
C /usr
C /usr/share
C /usr/share/nginx
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
C /run
A /run/nginx.pid
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp
A /var/cache/nginx/client_temp
複製代碼
最後咱們使用 commit
命令製做鏡像
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
# `-a` 做者信息
# `-m` 提交信息
複製代碼
docker commit \
-a "John Hannibal Smith <hannibal@a-team.com>" \
-m "修改了默認網頁" \
webserver \
nginx:v2
複製代碼
執行 docker images
能夠查看到咱們剛纔製做的鏡像。
docker history nginx:v2
# 能夠查看鏡像內的歷史記錄
複製代碼
咱們的製做的新鏡像,就至關於把容器的存儲層保存起來,放在原來鏡像的基礎之上。
使用 commit
製做鏡像,對鏡像的操做都是黑箱操做,咱們不知道具體到底更改了什麼,並且還容易把不少無用的東西也打包進來,這會讓鏡像很是臃腫。
爲了解決上述問題,咱們能夠經過 Dockerfile
來構建鏡像。Dockerfile
把構建鏡像的把每一層修改、安裝、構建、操做的命令都寫在一個文件中,它讓鏡像構建很是的透明。
咱們編寫 Dockerfile
給 docker client,docker client 給 docker server,由 docker server 來幫咱們製做鏡像。
咱們新建一個文件夾在裏面建立名稱爲 Dockerfile
的文件,在裏面寫上以下內容。
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html RUN echo '111' > /111 ENV ENV1=env1 ENV2=env2
RUN echo '222' > /222 RUN echo $ENV1 > env1 複製代碼
Dockerfile
是一個文本文件,其內包含了一條條的 指令,每一條指令構建一層,所以每一條指令的內容,就是描述該層應當如何構建。
Dockerfile
中 FROM
是必備的指令,而且必須是第一條指令!
它引入一個鏡像做爲咱們要構建鏡像的基礎層,就好像咱們首先要安裝好操做系統,才能夠在操做系統上面安裝軟件同樣。
docker
存在一個特殊的鏡像,名爲 scratch
。這個鏡像是虛擬的概念,並不實際存在,它表示一個空白的鏡像,若是你以 scratch
爲基礎鏡像的話,意味着你不以任何鏡像爲基礎,接下來所寫的指令將做爲鏡像第一層開始存在。
RUN
指令是用來執行命令行命令的。每個 RUN
指令都會新創建一層,在其上執行這些命令,咱們頻繁使用 RUN
指令會建立大量鏡像層,然而 Union FS 是有最大層數限制的,不能超過 127 層,並且咱們應該把每一層中我用文件清除,好比一些沒用的依賴,來防止鏡像臃腫。
咱們能夠這樣使用 RUN
指令
FROM debian:stretch
RUN buildDeps='gcc libc6-dev make wget' \ && apt-get update \ && apt-get install -y $buildDeps \ && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \ && mkdir -p /usr/src/redis \ && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \ && make -C /usr/src/redis \ && make -C /usr/src/redis install \ && rm -rf /var/lib/apt/lists/* \ && rm redis.tar.gz \ && rm -r /usr/src/redis \ && apt-get purge -y --auto-remove $buildDeps 複製代碼
ENV
指令用來設置環境變量,它有兩種形式:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
定義了環境變量,那麼在後續的指令中,就可使用這個環境變量。
CMD
指令用來在啓動容器的時候,指定默認的容器主進程的啓動命令和參數。
它有兩種形式
CMD echo 1
CMD ["npm", "run", "test"]
必須是雙引號第一種執行的命令會被包裝程,CMD [ "sh", "-c", "echo 1" ]
JSON 數組形式,通常推薦 JSON 數組形式。
容器中的應用都應該之前臺執行,而不是啓動後臺服務,容器內沒有後臺服務的概念。
對於容器而言,其啓動程序就是容器應用進程,容器就是爲了主進程而存在的,主進程退出,容器就失去了存在的意義。
好比 CMD service nginx start
它等同於 CMD [ "sh", "-c", "service nginx start"]
主進程其實是 sh
,sh
也就結束了,sh
做爲主進程退出了。
保存好文件後,咱們在當前文件夾下面執行
docker build -t nginx:v3 .
複製代碼
build
命令用來製做鏡像,-t
是給鏡像打標籤,-f
參數是指定 Dockerfile
路徑,因爲咱們使用的是默認 Dockerfile
名稱,因此能夠不一樣填寫該參數。
最後的 .
表明是當前路徑,它指定鏡像構建的上下文。咱們剛纔說過,真正製做鏡像的是 docker server
,當咱們執行 build
命令時,docker client
會將上下文路徑下的全部內容打包,而後上傳給 docker server
。這樣當咱們要在 Dockerfile
文件中執行 如 COPY
指令,就能夠將上下文中的文件複製到鏡像中去了。
通常應該會將 Dockerfile
置於一個空目錄下,或者項目根目錄下。若是該目錄下沒有所需文件,那麼應該把所需文件複製一份過來。若是目錄下有些東西確實不但願構建時傳給 Docker 引擎,那麼能夠用 .gitignore
同樣的語法寫一個 .dockerignore
。
當咱們執行完命令後控制檯會輸出
Sending build context to Docker daemon 2.048kB
Step 1/6 : FROM nginx
---> 719cd2e3ed04
Step 2/6 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
---> Running in 9ddbf675e4ea
Removing intermediate container 9ddbf675e4ea
---> c03b5364b185
Step 3/6 : RUN echo '111' > /111
---> Running in b0668bd11f96
Removing intermediate container b0668bd11f96
---> 42155bdf8812
Step 4/6 : ENV ENV1=env1 ENV2=env2
---> Running in 3ec6c29227e7
Removing intermediate container 3ec6c29227e7
---> 8b6243cb0c19
Step 5/6 : RUN echo '222' > /222
---> Running in dce84eae5105
Removing intermediate container dce84eae5105
---> 0a428b843fed
Step 6/6 : RUN echo $ENV1 > env1
---> Running in 0716ba5f87c1
Removing intermediate container 0716ba5f87c1
---> 21d774552f8c
Successfully built 21d774552f8c
Successfully tagged nginx:v3
複製代碼
咱們發現每一個指令都會有相似 Running in 3ec6c29227e7
這樣的輸出。
這是當構建鏡像時,docker 會啓動一個臨時的容器在上面執行指令,執行完畢後,咱們看到 Removing intermediate container 3ec6c29227e7
表明把這個臨時容器刪除,而後執行下一個執行的時候把當前構建好的這一層做爲它的基礎層。
就像 docker commit
命令同樣,保存它容器的存儲層,做爲新的鏡像層。
當咱們在執行一次 docker build -t nginx:v3 .
時
Sending build context to Docker daemon 2.048kB
Step 1/6 : FROM nginx
---> 719cd2e3ed04
Step 2/6 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
---> Using cache
---> c03b5364b185
Step 3/6 : RUN echo '111' > /111
---> Using cache
---> 42155bdf8812
Step 4/6 : ENV ENV1=env1 ENV2=env2
---> Using cache
---> 8b6243cb0c19
Step 5/6 : RUN echo '222' > /222
---> Using cache
---> 0a428b843fed
Step 6/6 : RUN echo $ENV1 > env1
---> Using cache
---> 21d774552f8c
Successfully built 21d774552f8c
Successfully tagged nginx:v3
複製代碼
會發現輸出 Using cache
,這是 docker 知道咱們的 Dockerfile
沒有變化,直接使用咱們上次構建的緩存層,這樣就不一樣從新構建了。
若是咱們修改一下 Dockerfile
文件
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html RUN echo '111' > /111 ENV ENV1=env1 ENV2=env2
RUN echo '!!!' RUN echo '222' > /222 RUN echo $ENV1 > env1 複製代碼
在中間新增了一條 RUN echo '!!!'
。而後在構建一次。看它的輸出。
Sending build context to Docker daemon 2.048kB
Step 1/7 : FROM nginx
---> 719cd2e3ed04
Step 2/7 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
---> Using cache
---> c03b5364b185
Step 3/7 : RUN echo '111' > /111
---> Using cache
---> 42155bdf8812
Step 4/7 : ENV ENV1=env1 ENV2=env2
---> Using cache
---> 8b6243cb0c19
Step 5/7 : RUN echo '!!!'
---> Running in 04ec83f30f71
!!!
Removing intermediate container 04ec83f30f71
---> 6ee208c0cd94
Step 6/7 : RUN echo '222' > /222
---> Running in 0286f9787e42
Removing intermediate container 0286f9787e42
---> 64fd449dd753
Step 7/7 : RUN echo $ENV1 > env1
---> Running in 996734b219e5
Removing intermediate container 996734b219e5
---> 322db42707c6
Successfully built 322db42707c6
Successfully tagged nginx:v3
複製代碼
咱們發現 RUN echo '!!!'
以後步驟都沒有用緩存了,所有都從新構建了。
docker run -it nginx:v3 bash
複製代碼
咱們執行這個命令去剛構建好的鏡像中去看看。
/# ls
111 222 bin boot dev env1 etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
/# echo $ENV1
env1
複製代碼
docker build
除了經過 Dockerfile
構建鏡像,還能夠經過 URL 構建,好比 git 倉庫
docker build https://gitrepo.com/git.git#:app
複製代碼
這行命令指定了構建所需的 Git repo,而且指定默認的 master
分支,構建目錄爲 /app/
。
多階段構建容許咱們在一個 Dockerfile
中編寫多個鏡像構建流程,而且後面的鏡像能夠複製前面文件。
好比咱們如今有一個前端項目,咱們要打包的時候須要用到 node
鏡像幫咱們打包生成最終的 html js css
文件,而後把最終把這幾個文件複製到 nginx
鏡像中,下次啓動咱們自定義的這個 nginx
鏡像就能夠看到咱們的網站了。
這時候咱們就須要用到兩個 Dockerfile
,一個用來打包,一個用來複制到 nginx
中。這樣讓部署過程很複雜。
這時候咱們就可使用多階段構建。
咱們須要安裝好 nodejs
環境,而後在命令行執行
npm init react-app myapp
複製代碼
而後去 myapp
文件夾,執行 npm start
就能夠看到默認頁面了。
而後咱們再 myapp
文件夾下編寫 .dockerignore
文件
.idea/
.vscode/
.git/
build/
node_modules/
npm-debug.log*
複製代碼
而後就是咱們的 Dockerfile
FROM node:alpine AS builder
WORKDIR /app
COPY ./package.json . RUN npm install --registry=https://registry.npm.taobao.org COPY . . RUN npm run build
FROM nginx AS prod
COPY --from=builder /app/build /usr/share/nginx/html/ 複製代碼
咱們首先引入 node:alpine
,alpine
是一個很是小的 linux 鏡像它只有 5MB
大小,咱們選擇的 node
鏡像的 alpine
版本,alpine
版本的鏡像表明鏡像很是的小,沒有安裝一些非必須的軟件等。
AS builder
默認每一個階段是沒有名稱的,咱們可使用 AS
給它一個名字,要引用它們能夠直接使用它的名稱,若是不指定的話,可使用從 0
開始的數字引用它們。
好比 COPY --from=builder /app/build /usr/share/nginx/html/
能夠寫成 COPY --from=0 /app/build /usr/share/nginx/html/
--from
不僅能夠是咱們構建中各個階段,也能夠是任意鏡像。
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf 複製代碼
WORKDIR
用來指定工做目錄,若是目錄不存在,WORKDIR 會幫你創建目錄。
咱們不想把咱們前端的文件直接放在根目錄中,而是想放在根目錄下的 app
文件夾,咱們就使用 WORKDIR
指令,之後咱們使用 ./
就是表明 /app
目錄了。
COPY
能夠將宿主機的文件複製到鏡像中。
COPY 源路徑... 目標路徑 複製代碼
源路徑能夠是多個,也能夠用通配符,文件匹配能夠查看 filepath.Match 規則
COPY hom* /mydir/ COPY hom?.txt /mydir/ 複製代碼
咱們先把 package.json
文件複製到臨時容器,在安裝依賴,而後才把項目文件複製到鏡像打包。
是由於這樣要是咱們的項目文件變更了而項目依賴沒有變化,咱們就可使用緩存而不須要從新安裝依賴。
最後咱們運行 build
命令,構建鏡像
docker build -t myapp:v1 .
複製代碼
若是咱們只想構建 builder
階段,可使用 target
參數
docker build --target builder -t imagename:tag .
複製代碼
構建完成咱們能夠查看一下咱們的鏡像
$ docker images
REPOSITORY TAG IMAGE ID SIZE
myapp v1 3d4af1e1a6bd 110MB
nginx latest 719cd2e3ed04 109MB
複製代碼
咱們發現咱們的構建鏡像只比 nginx
大 1MB
,很是的輕量。
最後咱們運行一下這個鏡像
docker run -d --rm --name myapp -p 80:80 myapp:v1
複製代碼
打開瀏覽器 127.0.0.1
就能夠看到效果。
咱們能夠把主機目錄做爲數據卷掛載到容器中去,容器中訪問掛在的文件時會被映射到咱們的主機目錄。
好比上面的前端項目,咱們能夠新建一個開發時的鏡像叫 Dockerfile.dev
FROM node:alpine
WORKDIR /app
COPY package.json . RUN npm install --registry=https://registry.npm.taobao.org COPY . .
EXPOSE 3000
CMD ["npm", "run", "start"] 複製代碼
EXPOSE <端口1> [<端口2>...]
複製代碼
申明要暴露的端口,這只是一個聲明,在運行時並不會由於這個聲明應用就會開啓這個端口的服務。
docker build -t myapp:dev -f Dockerfile.dev .
複製代碼
構建好鏡像後咱們能夠運行一個容器
docker run --rm --name myappdev -p 8000:3000 myapp:dev
複製代碼
而後咱們訪問本地的 8000
端口就可看到頁面了。
這時候咱們編輯咱們項目文件夾的文件,而後刷新瀏覽器,會發現沒有效果。由於瀏覽器中運行的頁面是咱們容器中的項目代碼而不是咱們本機的項目。
這時候咱們須要將容器的項目目錄映射到咱們本機的項目目錄,這樣咱們修改主機中的項目文件,就會反應到容器中。
echo 'CHOKIDAR_USEPOLLING=true' > .env
# 首先咱們須要新建 `.env` 文件並在其中寫入 `CHOKIDAR_USEPOLLING=true`
# 由於 watcher 在虛擬機內部須要使用輪詢模式
複製代碼
docker run --rm --name myappdev -v /app/node_modules -v $(pwd):/app -p 8000:3000 myapp:dev
複製代碼
在 windows 系統中不能使用 $(pwd)
,須要把它替換成項目的絕對路徑。
-v
的意思是將本機目錄映射到容器中,
-v 本機目錄(或文件):容器目錄(或文件)
# 本地目錄的路徑必須是絕對路徑
等同於 --mount type=bind,source=本機,target=容器
複製代碼
當咱們加入 -v
後面只有一個文件夾或文件時,表明容器中這個文件夾映射到一個匿名數據卷中。
上面的 -v /app/node_modules -v $(pwd):/app
表明映射當前目錄到容器的 /app
目錄,然而 node_modules
文件夾映射到一個匿名數據卷中,這樣就會讓 node_modules
這個文件夾不會被映射到咱們項目的文件夾。
默認掛在的是可讀可寫的,咱們可使用 ro
表明只可讀。
-v /src/webapp:/opt/webapp:ro
# 等同於 --mount type=bind,source=/src/webapp,target=/opt/webapp,readonly
複製代碼
最後咱們修改項目中 src/App.js
瀏覽器就會自動加載咱們修改後的數據了。
數據卷 是一個可供一個或多個容器使用的特殊目錄,它繞過 union file system
,能夠提供不少有用的特性。
docker volume create vol1
# 建立一個數據卷
docker volume ls
# 查看數據卷列表
docker volume inspect vol1
# 查看數據卷詳情
docker run -v vol1:/wepapp imagename:tag
# 掛載到容器裏
# 等同於 --mount source=my-vol,target=/webapp
docker volume rm vol1
# 刪除數據卷
docker volume prune
# 清理未使用的數據卷
複製代碼
Dockerfile
中有一個 VOLUME
指令,用來給容器中一個或多個文件夾掛在到,匿名數據卷中。
VOLUME ["<路徑1>", "<路徑2>"...] VOLUME <路徑> 複製代碼
這樣運行時若是用戶不指定掛載,其應用也能夠正常運行,不會向容器存儲層寫入大量數據。保證了容器存儲層的無狀態化。
固然咱們能夠在運行時覆蓋這個掛載設置。
docker run -v vol1:<路徑>
複製代碼
當容以運行一些網絡應用,要讓外部也能夠訪問這些應用,咱們經過 -P
或 -p
參數來指定端口映射。
-P
標記時,Docker 會隨機映射一個 49000~49900 的端口到內部容器開放的網絡端口。
docker run -d -p 127.0.0.1:80:80 nginx
# 映射到指定地址的指定端口
docker run -d -p 127.0.0.1::80 nginx
# 映射到指定地址的任意端口,本地主機會自動分配一個端口
docker run -d -p 80:80/udp nginx
# 還可使用 udp 標記來指定 udp 端口
docker run -d -p 80:80 -p 81:81 nginx
# 映射多個端口
複製代碼
docker port 容器
# 查看容器映射端口配置
複製代碼
當咱們有多個容器時須要它們之間互相鏈接,好比有 web
redis
和 mongodb
三個容器,web
服務器容器須要鏈接到 redis
和 mongodb
兩個數據庫。這時候可使用 docker network
命令。
$ docker network ls # 查看網絡列表
NETWORK ID NAME DRIVER SCOPE
f0d07aa56d2c bridge bridge local
ffff2c79740a host host local
ecad6ae9e643 none null local
複製代碼
當 Docker 啓動時,會自動在主機上建立一個 docker0
虛擬網橋,其實是 Linux 的一個 bridge,能夠理解爲一個軟件交換機。它會在掛載到它的網口之間進行轉發。
咱們看見默認有 3 個網絡,名稱爲 bridge
是咱們啓動一個容器時會自動鏈接到的 docker 虛擬網絡,它就是上面那個 docker0
。
host
表明直接跳過 docker 的虛擬網絡,直接使用主機的接口,這樣它少了安全保護,可是能夠提升吞吐量,通常不多使用獲得。
none
表明沒有使用主機的任何網絡,只使用 localhost
。
$ docker run -d --rm --name web1 nginx
# 啓動一個容器
$ docker network inspect bridge
# docker network inspect 能夠查看網絡詳情
...
"Containers": {
"cb3cf771c859303492fb29fab10e77bf401e129a5ffa7fc9ab062a941a383b58": {
"Name": "web1",
"EndpointID": "c44757274959d248bba6ad42cd83eef2457ed9de8bb0c861514312b39881fb7b",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
}
...
複製代碼
咱們看見咱們剛纔啓動的容器加入到可這個默認的虛擬網絡中。
docker run --rm alpine ping 172.17.0.2
# 咱們能夠嘗試 ping 一下 web1
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.083 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.064 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.064 ms
複製代碼
除了使用默認網絡,咱們還能夠本身建立虛擬網絡。
docker network create [OPTIONS] 網絡
# `-d` 指定驅動類型,默認 bridge
複製代碼
而後咱們運行的時候可使用 --network
參數指定一個或多個網絡,
docker run --network 網絡名 image
複製代碼
咱們能夠將不一樣的容器放在不一樣的網絡中,同一個網絡的容器能夠互相溝通,這樣咱們就無需暴露咱們主機的端口,容器在虛擬網絡中溝通。
咱們還能夠將一個容器加入一個網絡或者從一個網絡中退出。
$ docker network connect net1 containerName
# 鏈接到 net1
$ docker network disconnect net1 containerName
# 斷開 net1 鏈接
複製代碼
咱們本身建立的網絡會比默認的網絡多一個功能,那就是 DNS 解析功能。在上面咱們在默認 bridge 網絡中容器 ping
web1
須要使用 ip
地址。然而在咱們本身建立的網絡中只須要使用容器名就能夠。
$ docker network create net1 # 建立一個網絡
$ docker run -d --network net1 --rm --name server nginx
$ docker run -it --rm --network alpine ping server
PING server (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.160 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.065 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.066 ms
複製代碼
咱們發現咱們直接使用 server
這個容器名就能夠連到咱們的 nginx
服務器。
當咱們的容器多的時候,這很是的方便,也不用擔憂容器 ip
會發生改變。
咱們除了使用容器名鏈接,還可使用 --network-alias
指定別名鏈接,而且多個容器可使用同一個別名。這樣當鏈接到這個別名的時候,會隨機解析到一個其中一個容器,從而實現一個很是簡單負載均衡。
$ docker network create net2
$ docker run -d --network net2 --network-alias search elasticsearch:2
$ docker run -d --network net2 --network-alias search elasticsearch:2
# 建立兩個 elasticsearch 容器,它們使用同一個別名
$ docker run --rm --net net2 alpine nslookup search
# 看一下它們的 ip
Name: search
Address 1: 172.19.0.3 search.net2
Address 2: 172.19.0.2 search.net2
$ docker run --rm --net net2 centos curl -s search:9200
# 多執行幾回這個命令,會發現返回 name 會有不一樣。
複製代碼
爲了讓他人也可使用到咱們的鏡像,咱們能夠將將鏡像發佈到 Docker Hub 上。
首先咱們須要登陸到 docker hub
$ docker login -u username -p password
# 登陸到一個 Docker registry
$ docker push username/imageName:Tag
# 將鏡像推送到 Docker registry 上
$ docker pull username/imageName:Tag
# 而後就可使用 pull 命令將鏡像拉去到本地
複製代碼
在國內咱們可使用更快速的 阿里雲鏡像服務。
$ docker login -u username registry.cn-shanghai.aliyuncs.com
# 登陸到阿里雲
$ docker tag myapp:v1 registry.cn-shanghai.aliyuncs.com/wopen/myapp:v1
# 給咱們的鏡像打再打個標籤
$ docker push registry.cn-shanghai.aliyuncs.com/wopen/myapp:v1
# 推送到阿里雲鏡像服務。
$ docker pull registry.cn-shanghai.aliyuncs.com/wopen/myapp:v1
# 拉取到本地
$ docker logout registry.cn-shanghai.aliyuncs.com
# 從阿里雲退出登陸
複製代碼
更多更詳細的 docker 知識能夠直接去查看 docker 文檔。
咱們每次啓動一個容器都要一堆參數,當要一次啓動多個容器時更加麻煩,這時候就可使用 Docker Compose