初識Docker
什么是Docker
Docker是一個快速交付應用、運行應用的技術:
- 可以將程序及其依賴、運行環境一起打包為一個鏡像,可以遷移到任意Linux操作系統
- 運行時利用沙箱機制形成隔離容器,各個應用互不干擾
- 啟動、移除都可以通過一行命令完成,方便快捷
應用部署的環境問題
大型項目組件較多,運行環境也較為復雜,部署時會碰到一些問題
- 依賴關系復雜,容易出現兼容性問題
- 開發、測試、生產環境有差異
例如一個項目中,部署時需要依賴于node.js
、Redis
、RabbitMQ
、MySQL
等,這些服務部署時所需要的函數庫、依賴項各不相同,甚至會有沖突。給部署帶來了極大的困難。
Docker解決依賴兼容問題
Docker為了解決依賴的兼容問題的,采用了兩個手段:
-
將應用的Libs(函數庫)、Deps(依賴)、配置與應用一起打包
-
將每個應用放到一個隔離容器去運行,避免互相干擾
這樣打包好的應用包中,既包含應用本身,也保護應用所需要的Libs、Deps,無需再在操作系統上安裝這些,自然就不存在不同應用之間的兼容問題了。
雖然解決了不同應用的兼容問題,但是開發、測試等環境會存在差異,操作系統版本也會有差異,怎么解決這些問題呢?
Docker解決操作系統環境差異
要解決不同操作系統環境差異問題,必須先了解操作系統結構。以一個Ubuntu操作系統為例,結構如下:
結構包括:
- 計算機硬件:例如CPU、內存、磁盤等
- 系統內核:所有Linux發行版的內核都是Linux,例如CentOS、Ubuntu、Fedora等。內核可以與計算機硬件交互,對外提供內核指令,用于操作計算機硬件。
- 系統應用:將內核指令封裝為函數,以便程序員調用,用戶程序基于系統函數庫實現功能。
應用于計算機交互的流程如下:
1)應用調用操作系統應用(函數庫),實現各種功能
2)系統函數庫是對內核指令集的封裝,會調用內核指令
3)內核指令操作計算機硬件
Ubuntu
和CentOS
都是基于Linux內核,無非是系統應用不同,提供的函數庫有差異,此時,如果將一個Ubuntu
版本的MySQL
應用安裝到CentOS
系統,MySQL
在調用Ubuntu
函數庫時,會發現找不到或者不匹配,就會報錯了:
Docker如何解決大型項目依賴關系復雜,不同組件依賴的兼容性問題?
- Docker允許開發中將應用、依賴、函數庫、配置一起打包,形成可移植鏡像
- Docker應用運行在容器中,使用沙箱機制,相互隔離
Docker如何解決開發、測試、生產環境的差異問題?
- Docker將用戶程序與所需要調用的系統(比如Ubuntu)函數庫一起打包
- Docker鏡像中包含完整運行環境,包括系統函數庫,僅依賴系統的Linux內核,因此可以在任意Linux操作系統上運行
Docker與虛擬機的區別
Docker可以讓一個應用在任何操作系統中非常方便的運行。而以前我們接觸的虛擬機,也能在一個操作系統中,運行另外一個操作系統,保護系統中的任何應用。兩者有什么差異呢?
虛擬機(virtual machine)是在操作系統中模擬硬件設備,然后運行另一個操作系統,比如在 Windows 系統里面運行 Ubuntu 系統,這樣就可以運行任意的Ubuntu應用了。而Docker僅僅是封裝函數庫,并沒有模擬完整的操作系統。
Docker和虛擬機的差異總結:
-
docker是一個系統進程;虛擬機是在操作系統中的操作系統
-
docker體積小、啟動速度快、性能好;虛擬機體積大、啟動速度慢、性能一般。
Docker架構
鏡像和容器
Docker中有幾個重要的概念:
**鏡像(**Image):Docker將應用程序及其所需的依賴、函數庫、環境、配置等文件打包在一起,稱為鏡像。
容器(Container):鏡像中的應用程序運行后形成的進程就是容器,只是Docker會給容器做隔離,對外不可見。
一切應用最終都是代碼組成,都是硬盤中的一個個的字節形成的文件。只有運行時,才會加載到內存,形成進程。
鏡像,就是把一個應用在硬盤上的文件、及其運行環境、部分系統函數庫文件一起打包形成的文件包,這個文件包是只讀的;
容器,就是將這些文件中編寫的程序、函數加載到內存中運行,形成進程,只不過要隔離起來。因此一個鏡像可以啟動多次,形成多個容器進程。
例如你下載了一個QQ,如果我們將QQ在磁盤上的運行文件及其運行的操作系統依賴打包,形成QQ鏡像。然后你就可以啟動多次,雙開、甚至三開。
DockerHub
開源應用程序非常多,打包這些應用往往是重復的勞動。為了避免這些重復勞動,人們就會將自己打包的應用鏡像,例如Redis、MySQL鏡像放到網絡上,共享使用,就像GitHub的代碼共享一樣。
- DockerHub:DockerHub是一個官方的Docker鏡像的托管平臺。這樣的平臺稱為Docker Registry。
- 國內也有類似于DockerHub 的公開服務,比如 網易云鏡像服務、阿里云鏡像庫等。
我們一方面可以將自己的鏡像共享到DockerHub,另一方面也可以從DockerHub拉取鏡像。
Docker架構
我們要使用Docker來操作鏡像、容器,就必須要安裝Docker。
Docker是一個CS架構的程序,由兩部分組成:
-
服務端(server):Docker守護進程,負責處理Docker指令,管理鏡像、容器等
-
客戶端(client):通過命令或RestAPI向Docker服務端發送指令。可以在本地或遠程向服務端發送指令。
使用Docker
鏡像相關命令
首先來看下鏡像的名稱組成:
- 鏡像名稱一般分兩部分組成:[repository]:[tag]。
- 在沒有指定tag時,默認是latest,代表最新版本的鏡像
這里的mysql就是repository,5.7就是tag,合一起就是鏡像名稱,代表5.7版本的MySQL鏡像。
常見的鏡像操作命令:
docker images #查看鏡像
docker rmi #刪除鏡像
docker pull #拉取
docker push #推送
docker save #保存到文件
docker load #從文件加載
命令具體的參數可以使用--help
查看。
案例
從DockerHub中拉取一個nginx鏡像并查看
1)首先去鏡像倉庫搜索nginx鏡像,比如DockerHub:
2)根據查看到的鏡像名稱,通過命令:docker pull nginx
,拉取自己需要的鏡像
3)通過命令:docker images
查看拉取到的鏡像
利用docker save
將nginx鏡像導出磁盤,然后再通過load
加載回來
1)利用docker xx --help
命令查看docker save
和docker load
的語法
可以看到命令的格式為:
#docker save格式
docker save -o [保存的目標文件名稱] [鏡像名稱]#docker load格式
docker load -i [加載的目標文件名稱]
2)使用docker save導出鏡像到磁盤
運行命令:
docker save -o nginx.tar nginx:latest
3)使用docker load加載鏡像
先刪除本地的nginx鏡像:
docker rmi nginx:latest
然后運行命令,加載本地文件:
docker load -i nginx.tar
容器相關命令
其中:
-
docker run
:創建并運行一個容器,處于運行狀態- 常見參數:
--name
:指定容器名稱-p
:指定端口映射-d
:讓容器后臺運行
- 常見參數:
-
docker pause
:讓一個運行的容器暫停 -
docker unpause
:讓一個容器從暫停狀態恢復運行 -
docker stop
:停止一個運行的容器 -
docker start
:讓一個停止的容器再次運行 -
docker rm
:刪除一個容器-
不能刪除運行中的容器,除非添加 -f 參數強制刪除
-
容器狀態:
- 運行:進程正常運行
- 暫停:進程暫停,CPU不再運行,并不釋放內存
- 停止:進程終止,回收進程占用的內存、CPU等資源
-
-
docker logs
:查看容器日志- 添加
-f
參數可以持續查看日志
- 添加
-
docker ps
:查看容器狀態- 添加
-a
參數查看所有狀態的容器
- 添加
-
docker exec -it [容器名] [要執行的命令]
exec
命令可以進入容器修改文件,PS:但是在容器內修改文件是不推薦的
案例
創建并運行nginx容器的命令:
docker run --name [容器名稱] -p 80:80 -d [鏡像名稱]
這里的-p
參數,是將容器端口映射到宿主機端口。
默認情況下,容器是隔離環境,我們直接訪問宿主機的80端口,肯定訪問不到容器中的nginx。
現在,將容器的80與宿主機的80關聯起來,當我們訪問宿主機的80端口時,就會被映射到容器的80,這樣就能訪問到nginx了:
案例
進入Nginx容器,修改HTML文件內容,添加“Hello World!”
1)進入我們剛剛創建的nginx容器的命令為:
docker exec -it myniginx bash
命令解讀:
-
docker exec
:進入容器內部,執行一個命令 -
-it
: 給當前進入的容器創建一個標準輸入、輸出終端,允許我們與容器交互 -
myniginx:要進入的容器的名稱
-
bash
:進入容器后執行的命令,bash是一個linux終端交互命令
2)進入nginx的HTML所在目錄 /usr/share/nginx/html
cd /usr/share/nginx/html
容器內部會模擬一個獨立的Linux文件系統,看起來如同一個linux服務器一樣,nginx的環境、配置、運行文件全部都在這個文件系統中,包括我們要修改的html文件:
3)修改index.html的內容
容器內沒有vi命令,無法直接修改,我們用下面的命令來修改:
sed -i -e 's#Welcome to nginx#Hello Wolrd!#g' -e 's#<head>#<head><meta charset="utf-8">#g' index.html
數據卷(volume)
容器與數據耦合的問題
在之前的nginx案例中,修改nginx的html頁面時,需要進入nginx內部。并且因為沒有編輯器,修改文件也很麻煩。這就是因為容器與數據(容器內文件)耦合帶來的后果。
要解決這個問題,必須將數據與容器解耦,這就要用到數據卷了。
什么是數據卷?
數據卷是一個虛擬目錄,指向宿主機文件系統中的某個目錄。其作用是將容器與數據分離,解耦合,方便操作容器內數據,保證數據安全
一旦完成數據卷掛載,對容器的一切操作都會作用在數據卷對應的宿主機目錄了。這樣,我們操作宿主機的/var/lib/docker/volumes/html目錄,就等于操作容器內的/usr/share/nginx/html目錄了
數據卷操作命令
數據卷操作的基本語法如下:
docker volume [COMMAND]
docker volume
命令是數據卷操作,根據命令后跟隨的command來確定下一步的操作:
create
創建一個volumeinspect
顯示一個或多個volume的信息ls
列出所有的volumeprune
刪除未使用的volumerm
刪除一個或多個指定的volume
案例
創建一個數據卷,并查看數據卷在宿主機的目錄位置
① 創建數據卷
docker volume create html
② 查看所有數據
docker volume ls
③ 查看數據卷詳細信息卷
docker volume inspect html
可以看到,我們創建的html這個數據卷關聯的宿主機目錄為/var/lib/docker/volumes/html/_data
目錄。
④刪除數據卷
docker volume prune #刪除未使用的數據卷
docker volume rm 數據卷名稱 #刪除指定數據卷
掛載數據卷
我們在創建容器時,可以通過 -v
參數來掛載一個數據卷到某個容器內目錄,命令格式如下:
docker run --name [容器名稱] -v [數據卷名稱]:[目錄] -p 8080:80 [鏡像名稱]
例:
docker run --name mynginx -v html:/root/html -p 8080:80 [鏡像名稱]
這里的-v
就是掛載數據卷的命令:
-v html:/root/html
:把html數據卷掛載到容器內的/root/html這個目錄中,如果容器運行時volume不存在,會自動被創建出來
案例
創建一個nginx容器,修改容器內的html目錄內的index.html內容
分析:上個案例中,我們進入nginx容器內部,已經知道nginx的html目錄所在位置/usr/share/nginx/html
,我們需要把這個目錄掛載到html這個數據卷上,方便操作其中的內容。
提示:運行容器時使用 -v 參數掛載數據卷
步驟:
① 創建容器并掛載數據卷到容器內的HTML目錄
docker run --name mn -v html:/usr/share/nginx/html -p 80:80 -d nginx# 查看html數據卷的位置
docker volume inspect html
② 進入html數據卷所在位置,并修改HTML內容
# 進入該目錄
cd /var/lib/docker/volumes/html/_data
# 修改文件
vi index.html
**注意:**我在Windows下的Ubuntu中不能直接進入到/var/lib/docker/volumes/html/_data
這個目錄
根據網上的辦法執行以下指令進入到vm內部:
docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh
- –privileged : 表示允許該容器訪問宿主機中的各種設備
- –pid=host : 表示允許容器共享宿主機的進程命名空間(namespace),許容器看到宿主機中的各種進程
- nsenter : 是一個小工具 ns=namespace、enter=進入
- nsenter -t 1 -m -u -n -i sh
- -t 1: 表示要進入哪個pid,1表示整個操作系統的主進程id
- -m: 進入mount namespace,掛載點
- -u: 進入UTS namespace
- -n: 進入network namespace,網絡
- -i: 進入IPC namespace,進程間通信
- sh: 表示運行/bin/sh
掛載目錄
【案例】創建并運行一個MySQL容器,將宿主機目錄直接掛載到容器
- -v[宿主機目錄]:[容器內目錄]
- -v[宿主機文件]:[容器內文件]
-
導入
mysql
鏡像 -
創建目錄
/tmp/mysql/data
-
創建目錄
/tmp/mysql/conf
,hmy.cnf
文件上傳到/tmp/mysql/conf
-
去DockerHub查閱資料,創建并運行MySQL容器,要求:
? ①掛載/tmp/mysql/data
到mysql容器內數據存儲目錄
? ②掛載/tmp/mysql/conf/hmy.cnf
到mysql容器的配置文件
? ③設置MySQL密碼
docker run --name mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-p 3307:3306 \
-d \
-v /tmp/mysql/conf:/etc/mysql/conf.d \
-v /tmp/mysql/data:/var/lib/mysql \
mysql:latest
數據卷掛載與目錄直接掛載的區別
-
數據卷掛載耦合度低,由docker來管理目錄,但是目錄較深,不好找
-
目錄掛載耦合度高,需要我們自己管理目錄,不過目錄容易尋找查看
Dockerfile自定義鏡像
鏡像結構
鏡像是將應用程序及其需要的系統函數庫、環境、配置、依賴打包而成。
鏡像是分層結構,每一層稱為一個Layer
- BaseImage層:包含基本的系統函數庫、環境變量、文件系統;
- Entrypoint:入口,是鏡像中應用啟動的命令;
- 其它:在BaseImage基礎上添加依賴、安裝程序、完成整個應用的安裝和配置。
Dockerfile
Dockerfile就是一個文本文件,其中包含一個個的指令(Instruction),用指令來說明要執行什么操作以此來構建鏡像。每一個指令都會形成一層Layer。常用指令:
指令 | 說明 | 示例 |
---|---|---|
FROM | 指定基礎鏡像 | FROM centos:6 |
ENV | 設置環境變量 | ENV key value |
COPY | 拷貝本地文件到鏡像的指定目錄 | COPY ./mysql-5.7.rpm /tmp |
RUN | 執行Linux的shell命令,一般是安裝過程的命令 | RUN yum install gcc |
EXPOSE | 指定容器運行時監聽的端口,是給鏡像使用者看的 | EXPOSE 8080 |
ENTRYPOINT | 鏡像中應用的啟動命令,容器運行時調用 | ENTRYPOINT java -jar xx.jar |
更新詳細語法說明,請參考官網文檔: https://docs.docker.com/engine/reference/builder
注意:
-
Dockerfile的本質是一個文件,通過指令描述鏡像的構建過程;
-
Dockerfile第一行必須是FROM,從一個基礎鏡像來構建;
-
基礎鏡像可以是基本操作系統,如Ubuntu。也可以是其他人制作好的鏡像,例如:java:8-alpine。
構建Java項目
基本步驟
-
新建一個空文件夾
docker-demo
,并將docker-demo.jar
、jdk8.tar.gz
和Dockerfile
拷貝到該目錄下。Dockerfile
文件內容:# 指定基礎鏡像 FROM ubuntu:16.04 # 配置環境變量,JDK的安裝目錄 ENV JAVA_DIR=/usr/local# 拷貝jdk COPY ./jdk8.tar.gz $JAVA_DIR/# 安裝JDK RUN cd $JAVA_DIR \&& tar -xf ./jdk8.tar.gz \&& mv ./jdk1.8.0_144 ./java8# 配置環境變量 ENV JAVA_HOME=$JAVA_DIR/java8 ENV PATH=$PATH:$JAVA_HOME/bin#########################以上步驟都是在安裝jdk#############################Java項目包 COPY ./docker-demo.jar /tmp/app.jar # 暴露端口 EXPOSE 8090 # 入口,java項目的啟動命令 ENTRYPOINT java -jar /tmp/app.jar
-
運行命令:
docker build -t javaweb:1.0 .
基于openjdk:8-alpine
基于openjdk:8-alpine鏡像,將一個Java項目構建為鏡像
實現思路如下:
-
新建一個空的目錄,然后在目錄中新建一個文件,命名為
Dockerfile
-
拷貝
docker-demo.jar
到這個目錄中 -
編寫
Dockerfile
文件:# 指定基礎鏡像 FROM openjdk:8-alpine# 將docker-demo.jar拷貝到鏡像中 COPY ./docker-demo.jar /tmp/app.jar# 暴露端口 EXPOSS 8090 # 入口,Java項目的啟動命令 ENTRYPOINT java -jar /tmp/app.jar
-
使用
docker build
命令構建鏡像 -
使用
docker run
創建容器并運行 -
訪問成功