Docker 入門與實踐

Docker 是一個能讓程序跑在一個它 沒法感知的、用於 隔絕外界環境裏的容器的工具。

Docker 簡介

最初是 dotCloud 公司創始人 Solomon Hykes 發起的一個公司內部項目,並於 2013 年 3 月以 Apache 2.0 受權協議開源,代碼主要在 GitHub 上進行維護。Docker 項目後來還加入了 Linux 基金會,併成立推進 開放容器聯盟(OCI)html

Docker 使用 Google 推出的 Go 語言開發實現,基於 Linux 內核的 cgroupnamespace,以及 UnionFS 等技術。最初實現基於 LXC,從 0.7 版本後去除 LXC,轉而開始使用自行開發的 libcontainer,從 1.11 開始,則進一步演進爲使用 runCcontainerdnode

在 2017 年 4 月 21 日 Pull Request #32691 將原有的 Docker 項目改名爲 Moby,由 Moby 構建出 Docker CE(社區版),而新的 Docker 項目則構建出 Docker EE(企業版本)。mysql

Docker 初步瞭解

Docker 容器與虛擬機的區別

原理不一樣

F932548E-8278-415E-B844-DD50B33500A8.png

上圖是 Docker Doc 關於 Docker 和傳統虛擬機區別的截圖。linux

Docker 利用了 Linux 內核的 cgroup 和 namespace 爲程序的執行創造一個隔離的環境,使得程序感知不到外界的存在,其自己仍然是跑在原有的內核上的;而虛擬機則是經過 Hypervisor 模擬了一整套系統環境,虛擬機裏的程序是跑在虛擬機內核上的。因爲虛擬機須要模擬一整套操做系統環境,所以開銷比 Docker 容器要高不少不少。nginx

你能夠把跑在容器裏的程序想象成楚門(楚門的世界男主),他並不知道本身生活在一個精心佈置的超大影棚裏,可是他仍然是活在現實世界裏的,呼吸着現實世界中的空氣,吃着和咱們差很少的食物;跑在虛擬機裏的程序就好像活在動畫片裏的小豬佩奇,他的一切都是虛擬的,雖然小豬佩奇並不知道本身活在動畫片裏,可是很顯然的是它和咱們徹底不在一個世界(不是同一個系統內核)。git

性能差別

特性 容器 虛擬機
啓動 秒級 分鐘級
硬盤使用 通常爲 MB 通常爲 GB
性能 接近原生 弱於
系統支持量 單機上千個容器 通常幾十個

Docker 基本概念

鏡像

鏡像 是一個包含操做系統完整 root 文件系統 的、只讀的,由多層文件系統聯合而成的打包文件。

Docker 爲了讓應用無感知的跑在容器中,提供了一套完整的 root 文件系統,好比官方鏡像 library/ubuntu 就包含了一整套 root 文件系統。像 apache、nginx 都是基於該鏡像構建的,因爲 library/ubuntu 自己很大,因此 Docker 採用了分層存儲的方式。github

F8A2BBF4-F35D-4AC5-A897-8CDC0349E945.png

本文僞裝你已經安裝了 Docker,上圖經過 docker pull nginx官方 Registry(下面會提到這是啥)拉取 nginx 鏡像,拉取 nginx 至關於 library/nginx:latest,library 表示 nginx 是官方鏡像,所以能夠省略,:latest 表示拉取標籤爲 latest 的鏡像。拉取後能夠看到存在兩個鏡像,由於 nginx 鏡像自己就是基於 library:ubuntu:16.04 鏡像的。sql

3BFBA27D-6B51-445C-8D02-00F02A9A747A.png

上圖經過 docker pull httpd 拉取了 apache 鏡像,因爲 ubuntu:16.04 鏡像已經在本地存在了,所以拉取的時候不會重複拉取。從而節約拉取時間。這就是 Docker 分層存儲的意義。docker

鏡像的只讀能夠理解成之前的光盤 CD,是不可更改的。爲了模擬實現對光盤 CD 的寫的功能,會創建兩層文件系統,一層是光盤 CD 的只讀文件系統;另一層是存放更改數據的可寫的文件系統。從而實現模擬更改鏡像的做用。Docker 也是採用這種相似的分層的方式。shell

018E0260-D613-4098-A1E0-D0E6EC133014.png

如圖,能夠看出 ubuntu:15.04 是由不少層文件系統(鏡像)堆疊造成的,最底層是 root 文件系統(d3a1f33e8a5a)。這幾層文件系統都被設置成只讀的。多層文件系統利用了上面提到的 UnionFS、AUFS、OverlayFS,這是一類文件系統,這種聯合掛載文件系統最先就是用於解決 CD 這種只讀文件系統的修改問題,Docker 以前使用 AUFS,可是因爲 AUFS 不被 linus 喜歡(被 linus 評價爲稠密、不可讀,無註釋)致使 AUFS 一直沒有被合併到 Linux 的主分支中。Docker 在 1.12 之後已經將默認的文件系統從 AUFS 替換成 OverlayFS2。由於 OverlayFS2 已經被合併進了 Linux 的主幹分支中。

容器

上面咱們拉取了 nginx 鏡像到本地,咱們可使用 docker container start nginx(省略了 latest 標籤)來運行這個鏡像。運行以前會先建立一個容器(其實本質就是建立了一層可讀寫的文件系統,以提供程序運行時的讀寫支持),而後就會啓動程序,讓程序跑在一個隔離環境(不是虛擬環境)裏。你還能夠經過 docker container commit>來對當前層進行提交(就好像 Git 提交同樣),從而造成一個新的鏡像,可是這種方式是不推薦的;這是由於在程序運行過程當中可能會產生一些垃圾文件,而若是這些垃圾文件被提交後,新的鏡像又是不可修改的,只會增大鏡像的體積。具體怎麼建立鏡像會在下面說到。

container-layers.jpg

能夠看到上圖中在建立容器的時候其實就是建立了一個容器可讀寫層。你還能夠經過 docker container stop<container ID> 中止容器的運行,至關於 kill 掉容器內的正在運行的程序,可是建立容器時建立的可讀寫的文件系統依然存在。因此你依然能夠經過 docker start <container ID> 來重啓程序。

倉庫

鏡像構建完成後,能夠很容易的在宿主機器上運行,可是若是其餘機器要使用這個鏡像,咱們就須要一個集中存儲、分發鏡像的服務,Docker Registry 就是這樣的服務。一個 Docker Registry 能夠包含多個倉庫,每一個倉庫能夠包含多個標籤,每一個標籤對應一個鏡像。

就拿上面的 library/nginx:latest 舉例,library 表示這個鏡像是官方鏡像,若是不是官方鏡像,這裏通常填註冊在 Docker Registry 的用戶名;library/nginx 是倉庫的名字,latest 是該倉庫一個標籤。

誠然,官方的 Docker Registry 是世界上最大的鏡像分發服務,官方還提供了 Docker Registry 鏡像 用於搭建私有鏡像分發服務。並且 DockerHub 和社區一塊兒製做了大量的、高質量的鏡像,使得咱們構建鏡像更爲方便。

Docker 簡單實踐

Docker 單個鏡像

前面提到能夠經過 docker commit 生成新的鏡像,可是這種方式並不推薦(緣由已經說明),因此咱們通常仍是採用 Dockerfile 的方式。下面的實踐以 github-issue-rss 爲例,demonstate how to containerization a normal project。

首先建立一個 Dockerfile 文件,內容以下:

FROM node:9-alpine

MAINTAINER mrcode "mrcodehang@outlook.com"

WORKDIR /src # 表示容器內的程序運行時的當前目錄

COPY . /src # 把構建 Dockerfile 文件目錄下的文件所有複製到鏡像的 /src 目錄下
RUN npm install -g yarn && yarn install # 構建時執行

EXPOSE 3000 # 暴露容器的 3000 端口到外面

ENTRYPOINT ["npm", "start"] # 執行 docker start <container ID> 時就會執行 npm start

Dockerfile 裏的每一行開頭的大寫字母單詞叫作 Dockerfile 指令。每執行一條指令就會增長一層鏡像(本質是執行了一次 docker commit,而 AUFS 最大的層數是 127 層,所以 Dockerfile 裏的層數最好不要太多!

FROM 表示基於哪個鏡像構建,node:9-alpine 表示基於官方的 node 鏡像構建,標籤 9-alpine 表示這是一個 node 9 的鏡像,同時該 node9 鏡像是基於 alpine 鏡像構建的,alpine 是 Linux 的一個精簡發行版,大小隻有 5MB 左右,而 Ubuntu 鏡像大小接近 200MB。

RUN 指令會在構建鏡像時執行,使用 && 符號是爲了減小 RUN 命令的使用次數,減小最終鏡像的層數。

EXPOSE 指令讓外界能經過容器的 3000 端口進行網絡通訊。

ENTRYPOINT 表示執行 docker start <container ID> 時就會執行 npm start(啓動程序);還能夠寫成 ENTRYPOINT npm start 這種形式;而後就能夠開始構建了。有的同窗喜歡在 npm start 後加上 '&',來讓容器默認後臺運行;但這隻會致使容器沒法啓動,由於容器自己的執行徹底是依靠程序自己的進程的,當程序自己進程沒有掛載在 docker 容器上時,容器就會直接結束,容器結束後容器內的進程也被殺掉。因此要知道保持容器運行的正是容器內的進程自己!

39DC3EC7-D45C-468E-88C6-FD708197F228.png

圖中執行命令最後有一個 '.',這是將當前目錄做爲上下文傳遞給 Docker daemon;Docker 的工做方式是基於 C-S 架構的,你須要將構建的所在目錄傳給 docker daemon,這也是上面的 Dockerfile 文件的 COPY 指令的當前目錄

接下來建立容器,一個鏡像能夠建立多個容器(其實就是建立多個在同一層的讀寫層)。

314CECA0-D9C9-40D7-AFC7-FB8C43850A26.png

docker run 會拉取遠程的鏡像(若是本地沒有的話),接着它會建立一個容器,基於 mrcode/github-issue-rss:test 鏡像(只有 latest 標籤能夠省略);-v 會建立一個數據卷(volume),表示當容器對 /var/log/github-issue-rss/ 寫入數據時至關於寫在了宿主機的 ~/github-issue-rss/log 目錄上,從而維持容器的無狀態特性(無狀態特性是指容器在運行時儘可能不要將重要數據存儲在容器所在的讀寫層裏,雖然那是一層讀寫層,可是是用來存放程序運行時產生的臨時文件的,不該將重要數據放在裏面);-d 表示 daemon 執行程序,不然的話容器進程會掛載在當前 shell 上,通常經過 -d 掛載到 docker daemon 進程上;—rm 表示容器退出後自動刪除容器,這是推薦的用法,也是容器的無狀態特性的體現。

容器進程具備和容器內程序自己進程相同的生命週期,容器進程用來啓動容器內程序,至關於 Linux 內的 init 進程;當容器內程序被 docker stop <container ID> 殺掉時,容器就會退出,留下一個已建立的讀寫層文件系統,這也是容器存在的標誌。

因爲建立容器僅僅是建立了一個可讀寫的文件系統,因此容器的存在是很是很是輕量級的。即使對一個鏡像建立多個容器,鏡像自己是不會被從新拷貝的,而是最大程度的複用,這是由於鏡像內的多層文件系統的每一層都被設置成只讀的。

你能夠經過 docker container ls 查看當前正在運行的全部容器,若是還想查看已退出的容器,加上一個 -a 參數。使用 docker container start/stop 能夠啓動/關閉容器。

最後能夠經過 docker push mrcode/github-issue-rss:test 發佈到 DockerHub 上,分享到社區。

Docker 多個鏡像

github-issue-rssis a tool converts the issues on GitHub to RSS.

這個工具須要用到了 mysql,爲了之後方便數據遷移,我決定使用 mysql 鏡像,mysql 鏡像能夠把全部狀態存放在宿主機的一個文件夾下。那我如今不只須要啓動 mysql 和 github-issue-rss 鏡像,還須要創建他們之間的網絡鏈接關係,事情變得麻煩了。有一個工具叫 docker-compose (本文僞裝你已經安裝了這個工具)能夠把這一切自動化。下面是項目根目錄的一個 docker-compose.yml 文件:

version: "3"
services:
    db:
        image: mysql:5.7
        volumes:
            - ~/.github-issue-rss/mysql:/var/lib/mysql
        restart: always
        environment:
            MYSQL_ROOT_PASSWORD: rootroot
            MYSQL_DATABASE: rss
            MYSQL_USER: mrcode
            MYSQL_PASSWORD: github-issue-rss
    github-issue-rss:
        image: mrcode/github-issue-rss:v0.1.0
        depends_on:
            - db
        ports:
            - "3000:3000"
        restart: always
        environment:
            MYSQL_PORT: 3306
            MYSQL_HOST: db
            MYSQL_SCHEMA: rss
            MYSQL_USERNAME: mrcode
            MYSQL_PASSWORD: github-issue-rss
            LOG_FILE: /var/log/github-issue-rss/
        volumes:
            - ~/.github-issue-rss/log/:/var/log/github-issue-rss/

在 docker-compose 的世界裏沒有容器,只有服務。它認爲它啓動了兩服務 db 和 github-issue-rss。沒有哪一個是主服務,全部服務都是平等的。

在 db service 中,設置了 volumes,將 mysql 的數據存儲在 ~/.github-issue-rss/mysql/ 裏,還能夠設置更多的 volume。restart 表示只要服務執行失敗就重啓,防止依賴的 service 尚未啓動完成時致使的錯誤引起連鎖反應。給兩個 service 配置的 environment 來創建二者的數據鏈接,github-issue-rss 代碼會讀取這個環境變量,而後鏈接到 db 服務,能夠看到 github-issue-rss 裏的環境變量 MYSQL_HOST 設置爲 db,這是由於 docker-compose 會在啓動的服務配置裏創建這個 DNS 映射關係。

還能夠經過 docker-compose down 來中止而且刪除服務對應的容器。

如今你只須要克隆倉庫到本地,而後執行 docker-compose up 就能夠啓動 github-issue-rss 了,由於 github-issue-rss 鏡像自己已經構建併發布到 Docker Hub 了。

Docker 的應用

持續集成和持續交付

使用 Docker 能夠經過定製應用鏡像來實現持續集成、持續交付、部署。開發人員經過 Dockerfile 進行鏡像構建,結合持續集成系統進行集成測試,而運維人員則能夠在生產環境中快速部署該鏡像。甚至結合持續部署進行自動部署。

並且使用 Dockerfile 使鏡像的構建透明化,不只能夠幫助開發人員理解應用運行環境,也方便運維團隊理解應用運行所需條件,幫助更好的生產環境中部署該鏡像。

微服務

Docker 和微服務架構簡直就是渾然天成,站在 Docker 的角度,軟件本質是容器的組合:業務邏輯容器、數據庫容器、存儲容器、隊列容器……Docker 使得軟件拆分紅若干的標準化容器,而後像積木同樣的搭建起來。這正是微服務的思想:軟件把任務外包出去,讓各類外部服務完成這些任務,軟件自己只是底層服務的調用中心和組裝層。

參考文章

相關文章
相關標籤/搜索