Docker是一個開源的容器引擎,可讓開發者把他的應用和依賴環境打包到一個可移植的容器環境中。php
容器: 能夠理解爲一個輕量級的「虛擬機」,應用程序的運行環境。html
Docker的特色:python
虛擬機和容器的架構對比:mysql
經過對比容器和虛擬機的架構圖,虛擬機擁有獨佔的操做系統,虛擬化比較完全,容器是共享操做系統經過資源隔離使得容器擁有獨立的環境;虛擬機和容器都共享硬件資源。linux
所以容器比較「輕」,啓動一個容器可能就是一個進程,能夠秒級啓動容器。nginx
主要包含下面幾個部分:c++
下面以centos安裝docker ce社區版本爲例:git
//先安裝一些依賴的驅動和配置yum $ yum install -y yum-utils \ device-mapper-persistent-data \ lvm2 $ yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo //安裝docker $ yum install docker-ce docker-ce-cli containerd.io //啓動docker $ systemctl start docker //測試下是否安裝成功, 這裏運行一個hello-world鏡像,正常的話會輸出 Hello from Docker! $ docker run hello-world
下面經過打包一個靜態網站講解容器的基本用法。
首先對於一個靜態網站,咱們須要什麼樣的環境才能跑起來?github
對於靜態網站,咱們只須要一個Nginx軟件就能跑起來,下面一步步講解怎麼經過容器部署這個網站。
下面是網站的項目目錄結構:redis
├── Dockerfile // Docker鏡像定義文件 └── site //網站代碼目錄,只是一個簡單的靜態網站,只有一個頁面 └── index.html
建立鏡像,咱們首先須要定一個鏡像,定義鏡像的目的說白了就是咱們要在容器裏面安裝什麼東西。
下面是Dockerfile的定義:'#' 井號表示註釋.
#FROM 指令表示要繼承的鏡像,這裏繼承nginx官方鏡像,版本號是1.15.6 #定義鏡像,都是經過繼承某個已經存在的鏡像開始 FROM nginx:1.15.6 #設置工做目錄 WORKDIR /workspace #/usr/share/nginx/html目錄是nginx官方鏡像的默認網站目錄。 #複製site目錄的內容, 到/usr/share/nginx/html目錄。 COPY ./site/* /usr/share/nginx/html #刪除掉/workspace的內容,由於上面的指令已經把網站內容複製到指定的目錄,這裏的內容如今沒用了 RUN set -ex \ && rm -rf /workspace #重新設置工做空間 WORKDIR /usr/share/nginx/html #聲明容器須要暴露的端口 #EXPOSE 80 #容器啓動命令,這裏啓動nginx, 參數-g daemon off; 的意思是關閉nginx的守護進程模式。 #CMD ["nginx", "-g", "daemon off;"] # EXPOSE 和 CMD命令都被註釋掉了,由於nginx基礎鏡像已經定義過了,因此這裏不須要重複定義,這裏只是爲了講解目的,說明下容器是怎麼運行nginx的。
下面咱們經過docker客戶端建立鏡像。
//先切換到項目的根目錄 //經過docker build命令建立鏡像 //格式: docker build -t 鏡像名:版本號 上下文路徑 //上下文路徑的意思就是咱們須要把那個目錄上傳到docker守護進程做爲鏡像建立的工做目錄。 //這裏把當前目錄做爲上下文環境目錄 $ docker build -t demo1:v1.0.0 . 執行成功會輸出以下結果: Sending build context to Docker daemon 3.584kB //先打包上傳上下文目錄內容 Step 1/3 : FROM nginx:1.15.6 1.15.6: Pulling from library/nginx //下載nginx鏡像 a5a6f2f73cd8: Already exists 67da5fbcb7a0: Pull complete e82455fa5628: Pull complete Digest: sha256:31b8e90a349d1fce7621f5a5a08e4fc519b634f7d3feb09d53fac9b12aa4d991 Status: Downloaded newer image for nginx:1.15.6 ---> e81eb098537d //下面開始執行咱們Dockerfile定義的命令 Step 2/3 : WORKDIR /usr/share/nginx/html ---> Running in 155da3e24b72 Removing intermediate container 155da3e24b72 ---> 608d1e7fc6cd Step 3/3 : COPY --chown=nginx:nginx . /usr/share/nginx/html ---> 894f22f15a7c Successfully built 894f22f15a7c Successfully tagged demo1:v1.0.0 //查看當前機器上的鏡像,這個時候就看到剛纔建立的demo1鏡像 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE demo1 v1.0.0 894f22f15a7c 11 minutes ago 109MB
啓動容器的過程是: 根據鏡像啓動容器 -> 容器根據Dockerfile配置CMD命令啓動咱們應用。
下面啓動demo1鏡像,docker會建立一個容器運行nginx。
//經過docker run命令運行一個鏡像 //命令格式: docker run -p 容器綁定的外部端口:容器內部端口 鏡像:鏡像版本 $ docker run -p 8080:80 demo1:v1.0.0
經過瀏覽器訪問:http://localhost:8080 便可訪問網站
下面介紹Dockfile經常使用的指令
FROM是Dockerfile 文件的第一條指令,標示要繼承的鏡像。
格式:
FROM image[:tag]
參數說明:
image : 鏡像名或者鏡像地址。
tag : 可選參數,鏡像版本或者叫鏡像標籤。
例子:
#這裏使用的是官方nginx鏡像,標籤爲:1.15.6 FROM nginx:1.15.6 #這裏使用的是完整的鏡像地址,標籤爲:v1.3.0 FROM XXXX:v1.3.0
複製指令,主要用於複製目錄和文件。
格式:
COPY [--chown=user:group] src dest
參數說明:
src : 待複製的文件或者目錄
dest : 複製文件的目的地
--chown=user:group : 可選參數,設置複製文件或者目錄的用戶組
例子:
#複製./site/目錄下面的全部文件,到/usr/share/nginx/html目錄,同時設置文件的用戶和組爲nginx COPY --chown=nginx:nginx ./site/* /usr/share/nginx/html
用於執行命令
格式:
RUN command
只有一個參數command, 就是咱們想要執行的命令。
用於設置容器默認執行的命令。
格式:
CMD ["executable","param1","param2"]
參數說明:
executable : 執行程序,能夠包含完整路徑
param1, param2 ...param N : 執行程序的任意參數
設置容器環境變量
格式:
ENV = ...
例子:
#用空格分隔鍵值對 ENV myName="John Doe" myCat="fluffy"
用於設置執行RUN, CMD 和 ENTRYPOINT命令時候的以什麼用的身份運行。
格式:
USER user[:group]
參數說明:
user : 用戶名
group : 可選參數,用戶組
注意:若是設置的用戶不存在,則以root用戶身份運行。 例子:
#設置執行RUN、CMD、ENTRYPOINT的時候用戶和組爲www
USER www:www
用於爲RUN、CMD、ENTRYPOINT、COPY命令設置工做目錄。若是設置的工做目錄不存在,則自動建立一個。
例子:
WORKDIR /path/to/workdir
docker build命令在發送上下文目錄的文件到docker守護進程以前,能夠經過.dockerignore配置文件,設置須要過濾的文件或者目錄。
.dockerignore文件保存在上下文目錄的根目錄。
.dockerignore例子:
.dockerignore配置語法相似.gitignore
# comment #忽略全部目錄下面的.log後綴的文件 **/*.log #忽略data目錄 /data #忽略全部的.md結尾的文件,除了README.md *.md !README.md
鏡像管理問題:
爲管理鏡像咱們須要一個相似github的鏡像倉庫,Docker官方爲咱們提供了一個公開的鏡像倉庫。 官方倉庫地址:https://hub.docker.com/, 在上面能夠找到各類各樣的鏡像,相似nginx、redis、mysql這些知名的開源軟件官方也到這裏發佈了本身的官方鏡像。
提示:知名開源軟件提供的官方鏡像會有Official Image標記, 這些鏡像會比較可靠,其餘的鏡像都是網友上傳的。
下載鏡像不須要帳號密碼,若是要發佈本身的鏡像就須要註冊賬號。
對於我的使用公開鏡像固然沒問題,可是對於公司就不行,對於公司要麼本身搭建一個私有的鏡像倉庫,要麼使用阿里雲這種雲平臺提供的鏡像服務,建立一個私有倉庫。
不管是本身搭建的鏡像倉庫仍是使用公開的鏡像倉庫用法是同樣的。
鏡像倉庫的用法相似git, 下面是鏡像倉庫的基本用法:
#登錄倉庫命令: #公開倉庫不須要登陸 $ docker login --username=賬號 倉庫地址 #例子: #登錄阿里雲容器服務倉庫 #回車後輸入密碼便可 docker login --username=123456@qq.com XXX #下載鏡像, 命令格式 docker pull 鏡像:[鏡像版本號] #例子1: #下載nginx最新鏡像 docker pull nginx #下載阿里雲鏡像倉庫的鏡像,鏡像版本號爲:v1.0.0 docker pull XXX:v1.0.0 #上傳鏡像,命令格式 docker push 鏡像:[鏡像版本號] #例子: #推送鏡像,以前docker login命令登錄到什麼倉庫,就推送到那裏。 docker push XXX:v1.0.0
一個Docker鏡像是有多個layer(層)組成的;每一層都存儲一些文件數據,每一層都是在另外一層的基礎上進行CRUD操做,每一層只是記錄相對前面幾層的差別部分的文件數據,Docker鏡像分層技術,很像git代碼管理,git記錄每一次代碼文件的變化歷史,每一次提交的版本都是上一個版本的差別部分。
注意:鏡像每一層的文件數據都是隻讀的。
當建立一個容器的時候,會在頂層增長一層可寫的容器層,咱們容器的數據都是寫在這上面。
容器層的數據不是持久化的,刪除容器數據就丟了。
例子:
Dockerfile定義以下
FROM ubuntu:15.04 COPY . /app RUN make /app CMD python /app/app.py
dockerfile由4條指令構成,建立鏡像後,分層示意圖以下:
從底層往上看,最底下的一層保存dockerfile的FROM指令運行後產生的數據,倒數第二層對應COPY指令複製文件產生的數據,以此類推。
你們能夠看到每一層都一個ID,例如最底下一層的id爲: d3a1f33e8a5a, 很像git的提交記錄吧。
當根據鏡像建立容器的時候會在頂層增長一層容器層(Container layer), 保存容器運行的數據。
提示:docker能夠分層緩存數據,若是本地鏡像已經下載過對應層的數據,那麼docker不會重複下載對應的層;因此有些鏡像看起來幾百M,可是瞬間就下載完成,緣由是分層緩存對應的數據。
下圖是根據同一個鏡像建立多個容器的狀況:
每個容器都有本身單獨的容器層(Container layer),容器之間數據是獨立的;可是他們底層的鏡像數據是同樣的。
在Docker容器裏面寫文件數據,這些數據不是持久化的,隨着容器被刪除數據將會丟失,下面是Docker提供的容器文件數據管理方式。
主要支持下面三種方式管理文件數據:
提示:不管使用那種方式管理容器文件數據,對容器內部來講,看起來跟本地文件系統同樣。區別就是真實數據保存在什麼地方。
容器管理文件數據示意圖:
使用Volumes方式管理文件數據須要先建立volume數據卷,而後掛載到容器裏面。
例子:
//建立數據卷 demo1-data $ docker volume create demo1-data //查看當前機器建立的數據卷 $ docker volume ls #輸出 DRIVER VOLUME NAME local demo1-data //查看數據卷詳情, 能夠查看數據具體保存在什麼地方 $ docker volume inspect demo1-data #輸出 [ { "CreatedAt": "2019-04-18T15:59:58+08:00", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/demo1-data/_data", "Name": "demo1-data", "Options": {}, "Scope": "local" } ] //刪除數據卷 $ docker volume rm demo1-data //將建立好的數據卷掛載到容器裏面 $ docker run -d --name demo1 -v demo1-data:/mnt/data demo1:v1.0.0 參數說明: -d : 讓容器在後臺運行. --name : 給容器起個名字. -v : 掛載數據卷, 格式: -v 數據卷名字:容器目錄 demo1:v1.0.0 : 鏡像的名字
任意目錄或者文件掛載方式.
例子:
//通常咱們本地開發啓動nginx,配置文件都是在本地,咱們但願容器能夠讀取本地的nginx配置。 //這個例子是把主機上的nginx配置文件掛載到nginx容器的配置文件裏面, 這裏掛載的是文件。 $ docker run --name my-custom-nginx-container -v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx 參數說明: --name my-custom-nginx-container : 容器名字 -v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro : -v 掛載參數,將主機上的/host/path/nginx.conf文件掛載到容器的/etc/nginx/nginx.conf文件,ro 參數只讀的意思。 //除了上面的掛載文件,咱們也能夠掛載目錄, 接着上面的例子 $ docker run --name my-custom-nginx-container -v /host/path/nginx/conf:/etc/nginx/:ro -d nginx 參數說明: -v /host/path/nginx/conf:/etc/nginx/:ro : 這裏把主機的/host/path/nginx/conf目錄以只讀方式掛載到容器的/etc/nginx/目錄。
tmpfs主要是將數據存儲在主機內存中。
例子:
$ docker run -d --name demo1 --tmpfs /mnt/data demo1:v1.0.0 參數說明: --tmpfs /mnt/data : 掛載主機內存空間到容器的/mnt/data目錄
在實際應用的時候咱們每每會啓動多個容器,分別運行不一樣應用,又或者爲了負載均衡啓動大量的容器實例;那麼容器之間怎麼通訊?
容器之間的通訊方式跟服務器之間的通訊方式差很少,除此以外在同一臺主機的容器之間Docker支持直接互相鏈接的通訊方式。
容器之間的通訊方式大致上有兩種:
例子1: 同一臺主機上的容器之間經過link參數互聯.
//啓動一個redis server,容器名字叫: redis, redis其餘參數配置默認 $ docker run -d --name redis //另外啓動一個容器,鏈接redis容器, 這樣在demo1容器內部可使用redis名字當成redis服務器地址 $ docker run -d --name demo1 --link redis demo1:v1.0.0 參數說明: --link :鏈接容器, 格式: --link 容器名 , 鏈接多個容器,可使用多個--link參數便可
例子2:主機怎麼訪問容器裏面的應用?
例如,本地開發測試,咱們使用容器運行應用,那麼容器外面的主機怎麼訪問?
容器須要暴露容器內部的端口給主機,主機才能訪問容器內部的應用。
//例如,咱們使用nginx容器部署了一個網站應用,nginx容器須要暴露80端口出來。 $ docker run --name myapp -p 8080:80 -d nginx 參數說明: -p : 綁定端口參數,格式 -p 主機端口:容器內部端口
主機端口能夠任意選擇,只要沒被其餘程序佔用便可。
主機端口不必定要跟容器內部端口一致, 只是咱們習慣用同樣的端口,便於理解。
//查看正在運行的容器 $ docker ps //啓動容器 - 容器建立以後不須要再次使用docker run命令建立容器, 這個時候只要啓動容器便可。 //啓動容器名爲redis的容器 $ docker start redis //關閉容器 $ docker stop redis //刪除容器 $ docker rm redis //容器的啓動、關閉、刪除除了使用容器名字以外,還可使用容器id //例如: $ docker stop f648f34beabb //執行容器裏面的命令 $ docker exec -it redis bash 參數說明: -it : 打開一個交互的終端。 redis : 容器的名字叫作redis bash : 執行容器裏面的bash命令 //上面這條命令的其實就是鏈接容器打開一個shell窗口,相似登陸服務器同樣。 // 命令格式: docker exec 容器名 要執行的命令 //刪除鏡像 docker image rm 鏡像名 //給鏡像打標籤 docker tag 鏡像id 標籤 例子: $ docker tag ImageId xxxx:v2.0.1 //清理未使用的鏡像 $ docker image prune
不安裝跟應用無關的軟件,清理掉沒有用的文件; 例如咱們在編譯安裝軟件的是最產生不少中間文件,安裝成功後能夠刪除這些數據。
由於Docker鏡像的層級是有限的,在Dockfile配置文件中FROM、COPY、RUN這些指令都會產生layer, 用的最多的主要是RUN指令,推薦把連續的多條RUN指令合併成一條。
例子:
FROM centos COPY . /app WORKDIR /app #安裝依賴包 #下面每一條RUN指令都會產生一個layer, 光是RUN指令就產生了6個layer RUN yum -y install gcc automake autoconf libtool make gcc-c++ #編譯安裝 RUN ./configure RUN make RUN make install #清理安裝文件 RUN rm -rf /app #清理yum 緩存 RUN yum clean all
優化後的Dockfile
FROM centos COPY . /app WORKDIR /app #經過&&把多條命令連成一條命令,每行末尾的反斜槓是爲了可讀性,一行書寫一條命令。 #這裏只會產生一個layer RUN yum -y install gcc automake autoconf libtool make gcc-c++ \ && ./configure \ && make \ && make install \ && rm -rf /app \ && yum clean all
由於在實際生產環境中容器是常常被銷燬、重建的,多是由於應用異常容器退出了,也多是某臺服務器負載過高,容器被調度到別的服務器運行; 當容器被銷燬的時候存儲在容器的數據會丟失。
生產環境中存儲持久化數據通常有如下方案:
要保證容器的「輕」的特性,咱們通常不會把容器當成服務器來用,什麼東西都往裏面安裝。
例如:咱們常常會在一臺服務器上安裝nginx、php、mysql、redis,同時部署一堆應用系統。
若是在容器這麼幹,升級維護就變得複雜了,並且不容易擴展,若是要升級redis,或者升級某個應用系統就得你們一塊兒更新,並且還無法對裏面的單個應用進行擴容。
一個容器跑一個應用,能夠保證容器的輕量級特性,又相對獨立,便於單獨升級和擴容。
例如:一個PHP系統,須要nginx、 php fpm、 mysql、redis 那麼鏡像和容器能夠這麼設計。
鏡像製做狀況:
容器狀況:
根據上面的鏡像製做狀況,至少須要同時運行三個容器,上面的三個鏡像分別創一個容器。
一個項目或者一個公司的基礎環境通常都是同樣的,沒有必要每次都從新制做基礎環境鏡像。
例如一個項目的基礎環境是: nginx + php-fpm + PHP擴展庫(curl、kafka、redis、swoole), 咱們只要先根據環境須要製做一個基礎鏡像,項目只要繼承這個基礎鏡像,而後設置項目本身的參數便可。
容器經過CMD設置應用的啓動命令,當容器啓動應用的主進程後,應用主進程的生命週期就跟容器捆綁了,主進程退出,容器就退出了。
這個地方跟咱們通常服務器部署應用不同, 通常在服務器部署應用都是把進程切換到後臺保持運行就能夠,容器部署應用不能切換到後臺運行,不然容器會任務應用結束了,容器本身就退出了。
建立容器的時候能夠經過-d參數把容器切換到後臺運行,例如: docker run -d --name redis redis