我們在部署一個項目時,會出現一系列問題比如:
- 命令太多了,記不住
- 軟件安裝包名字復雜,不知道去哪里找
- 安裝和部署步驟復雜,容易出錯
其實上述問題不僅僅是新手,即便是運維在安裝、部署的時候一樣會覺得麻煩、容易出錯。
特別是我們即將進入微服務階段學習,微服務項目動輒就是幾十臺、上百臺服務需要部署,有些大型項目甚至達到數萬臺服務。而由于每臺服務器的運行環境不同,你寫好的安裝流程、部署腳本并不一定在每個服務器都能正常運行,經常會出錯。這就給系統的部署運維帶來了很多困難。
那么,有沒有一種技術能夠避免部署對服務器環境的依賴,減少復雜的部署流程呢?
答案是肯定的,這就是我們今天要學習的Docker技術。你會發現,有了Docker以后項目的部署如絲般順滑,大大減少了運維工作量。
即便你對Linux不熟悉,你也能輕松部署各種常見軟件、Java項目。
通過今天的學習,希望大家能達成下面的學習目標:
- 能利用Docker部署常見軟件
- 能利用Docker打包并部署Java應用
- 理解Docker數據卷的基本作用
- 能看懂DockerCompose文件
1.快速入門
要想讓Docker幫我們安裝和部署軟件,肯定要保證你的機器上有Docker. 由于大家的操作系統各不相同,安裝方式也不同。為了便于大家學習,我們統一在CentOS的虛擬機中安裝Docker,統一學習環境。
注意:使用MacBook的同學也請利用 VMwareFusion來安裝虛擬機,并在虛擬機中學習Docker使用。
安裝方式參考文檔:《安裝Docker》
1.1.部署MySQL
首先,我們利用Docker來安裝一個MySQL軟件,大家可以對比一下之前傳統的安裝方式,看看哪個效率更高一些。
如果是利用傳統方式部署MySQL,大概的步驟有:
- 搜索并下載MySQL安裝包
- 上傳至Linux環境
- 編譯和配置環境
- 安裝
而使用Docker安裝,僅僅需要一步即可,在命令行輸入下面的命令(建議采用CV大法):
docker run -d \--name mysql \-p 3306:3306 \-e TZ=Asia/Shanghai \-e MYSQL_ROOT_PASSWORD=123 \mysql
運行效果如圖:
MySQL安裝完畢!通過任意客戶端工具即可連接到MySQL.
大家可以發現,當我們執行命令后,Docker做的第一件事情,是去自動搜索并下載了MySQL,然后會自動運行MySQL,我們完全不用插手,是不是非常方便。
而且,這種安裝方式你完全不用考慮運行的操作系統環境,它不僅僅在CentOS系統是這樣,在Ubuntu系統、macOS系統、甚至是裝了WSL的Windows下,都可以使用這條命令來安裝MySQL。
要知道,不同操作系統下其安裝包、運行環境是都不相同的!如果是手動安裝,必須手動解決安裝包不同、環境不同的、配置不同的問題!
而使用Docker,這些完全不用考慮。就是因為Docker會自動搜索并下載MySQL。注意:這里下載的不是安裝包,而是鏡像。鏡像中不僅包含了MySQL本身,還包含了其運行所需要的環境、配置、系統級函數庫。因此它在運行時就有自己獨立的環境,就可以跨系統運行,也不需要手動再次配置環境了。這套獨立運行的隔離環境我們稱為容器。
說明:
- 鏡像:英文是
image
- 容器:英文是
container
因此,Docker安裝軟件的過程,就是自動搜索下載鏡像,然后創建并運行容器的過程。
Docker會根據命令中的鏡像名稱自動搜索并下載鏡像,那么問題來了,它是去哪里搜索和下載鏡像的呢?這些鏡像又是誰制作的呢?
Docker官方提供了一個專門管理、存儲鏡像的網站,并對外開放了鏡像上傳、下載的權利。Docker官方提供了一些基礎鏡像,然后各大軟件公司又在基礎鏡像基礎上,制作了自家軟件的鏡像,全部都存放在這個網站。這個網站就成了Docker鏡像交流的社區:
https://hub.docker.com/
基本上我們常用的各種軟件都能在這個網站上找到,我們甚至可以自己制作鏡像上傳上去。
像這種提供存儲、管理Docker鏡像的服務器,被稱為DockerRegistry
,可以翻譯為鏡像倉庫。DockerHub
網站是官方倉庫,阿里云、華為云會提供一些第三方倉庫,我們也可以自己搭建私有的鏡像倉庫。
官方倉庫在國外,下載速度較慢,一般我們都會使用第三方倉庫提供的鏡像加速功能,提高下載速度。而企業內部的機密項目,往往會采用私有鏡像倉庫。
總之,鏡像的來源有兩種:
- 基于官方基礎鏡像自己制作
- 直接去
DockerRegistry
下載
總結一下:
Docker本身包含一個后臺服務,我們可以利用Docker命令告訴Docker服務,幫助我們快速部署指定的應用。Docker服務部署應用時,首先要去搜索并下載應用對應的鏡像,然后根據鏡像創建并允許容器,應用就部署完成了。
用一幅圖標示如下:
1.2.命令解讀
利用Docker快速的安裝了MySQL,非常的方便,不過我們執行的命令到底是什么意思呢?
docker run -d \--name mysql \-p 3306:3306 \-e TZ=Asia/Shanghai \-e MYSQL_ROOT_PASSWORD=123 \mysql
解讀:
docker run -d
:創建并運行一個容器,-d則是讓容器以后臺進程運行--name mysql
: 給容器起個名字叫mysql,你可以叫別的-p 3306:3306
: 設置端口映射。
- 容器是隔離環境,外界不可訪問。但是可以將宿主機端口映射容器內到端口,當訪問宿主機指定端口時,就是在訪問容器內的端口了。
- 容器內端口往往是由容器內的進程決定,例如MySQL進程默認端口是3306,因此容器內端口一定是3306;而宿主機端口則可以任意指定,一般與容器內保持一致。
- 格式:
-p 宿主機端口:容器內端口
,示例中就是將宿主機的3306映射到容器內的3306端口-e TZ=Asia/Shanghai
: 配置容器內進程運行時的一些參數
- 格式:
-e KEY=VALUE
,KEY和VALUE都由容器內進程決定- 案例中,
TZ=Asia/Shanghai
是設置時區;MYSQL_ROOT_PASSWORD=123
是設置MySQL默認密碼mysql
: 設置鏡像名稱,Docker會根據這個名字搜索并下載鏡像
- 格式:
REPOSITORY:TAG
,例如mysql:8.0
,其中REPOSITORY
可以理解為鏡像名,TAG是版本號- 在未指定TAG的情況下,默認是最新版本,也就是
mysql:latest
鏡像的名稱不是隨意的,而是要到DockerRegistry中尋找,鏡像運行時的配置也不是隨意的,要參考鏡像的幫助文檔,這些在DockerHub網站或者軟件的官方網站中都能找到。
如果我們要安裝其它軟件,也可以到DockerRegistry中尋找對應的鏡像名稱和版本,閱讀相關配置即可。
2.Docker基礎
接下來,我們一起來學習Docker使用的一些基礎知識,為將來部署項目打下基礎。具體用法可以參考Docker官方文檔:
https://docs.docker.com/
2.1.常見命令
首先我們來學習Docker中的常見命令,可以參考官方文檔:
https://docs.docker.com/engine/reference/commandline/cli/
2.1.1.命令介紹
其中,比較常見的命令有:
用一副圖來表示這些命令的關系:
補充:
默認情況下,每次重啟虛擬機我們都需要手動啟動Docker和Docker中的容器。通過命令可以實現開機自啟:
# Docker開機自啟
systemctl enable docker# Docker容器開機自啟
docker update --restart=always [容器名/容器id]
2.1.2.演示
教學環節說明:我們以Nginx為例給大家演示上述命令。
# 第1步,去DockerHub查看nginx鏡像倉庫及相關信息# 第2步,拉取Nginx鏡像
docker pull nginx# 第3步,查看鏡像
docker images
# 結果如下:
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 16 months ago 141MB
mysql latest 3218b38490ce 17 months ago 516MB# 第4步,創建并允許Nginx容器
docker run -d --name nginx -p 80:80 nginx# 第5步,查看運行中容器
docker ps
# 也可以加格式化方式訪問,格式會更加清爽
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"# 第6步,訪問網頁,地址:http://虛擬機地址# 第7步,停止容器
docker stop nginx# 第8步,查看所有容器
docker ps -a --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"# 第9步,再次啟動nginx容器
docker start nginx# 第10步,再次查看容器
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"# 第11步,查看容器詳細信息
docker inspect nginx# 第12步,進入容器,查看容器內目錄
docker exec -it nginx bash
# 或者,可以進入MySQL
docker exec -it mysql mysql -uroot -p# 第13步,刪除容器
docker rm nginx
# 發現無法刪除,因為容器運行中,強制刪除容器
docker rm -f nginx
2.1.3.命令別名
給常用Docker命令起別名,方便我們訪問:
# 修改/root/.bashrc文件
vi /root/.bashrc
內容如下:
# .bashrc# User specific aliases and functionsalias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
alias dps='docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"'
alias dis='docker images'# Source global definitions
if [ -f /etc/bashrc ]; then. /etc/bashrc
fi
然后,執行命令使別名生效
source /root/.bashrc
接下來,試試看新的命令吧。
2.2.數據卷
容器是隔離環境,容器內程序的文件、配置、運行時產生的容器都在容器內部,我們要讀寫容器內的文件非常不方便。大家思考幾個問題:
- 如果要升級MySQL版本,需要銷毀舊容器,那么數據豈不是跟著被銷毀了?
- MySQL、Nginx容器運行后,如果我要修改其中的某些配置該怎么辦?
- 我想要讓Nginx代理我的靜態資源怎么辦?
因此,容器提供程序的運行環境,但是程序運行產生的數據、程序運行依賴的配置都應該與容器解耦。
2.2.1.什么是數據卷
數據卷(volume)是一個虛擬目錄,是容器內目錄與宿主機目錄之間映射的橋梁。
以Nginx為例,我們知道Nginx中有兩個關鍵的目錄:
- html:放置一些靜態資源
- conf:放置配置文件
如果我們要讓Nginx代理我們的靜態資源,最好是放到html
目錄;如果我們要修改Nginx的配置,最好是找到conf下的nginx.conf
文件。
但遺憾的是,容器運行的Nginx所有的文件都在容器內部。所以我們必須利用數據卷將兩個目錄與宿主機目錄關聯,方便我們操作。如圖:
在上圖中:
- 我們創建了兩個數據卷:
conf
、html
- Nginx容器內部的
conf
目錄和html
目錄分別與兩個數據卷關聯。 - 而數據卷
conf
和html
分別指向了宿主機的/var/lib/docker/volumes/conf/_data
目錄和/var/lib/docker/volumes/html/_data
目錄
這樣以來,容器內的conf
和html
目錄就 與宿主機的conf
和html
目錄關聯起來,我們稱為掛載。此時,我們操作宿主機的/var/lib/docker/volumes/html/_data
就是在操作容器內的/usr/share/nginx/html/_data
目錄。只要我們將靜態資源放入宿主機對應目錄,就可以被Nginx代理了。
小提示:
/var/lib/docker/volumes
這個目錄就是默認的存放所有容器數據卷的目錄,其下再根據數據卷名稱創建新目錄,格式為/數據卷名/_data。
為什么不讓容器目錄直接指向宿主機目錄呢?
- 因為直接指向宿主機目錄就與宿主機強耦合了,如果切換了環境,宿主機目錄就可能發生改變了。由于容器一旦創建,目錄掛載就無法修改,這樣容器就無法正常工作了。
- 但是容器指向數據卷,一個邏輯名稱,而數據卷再指向宿主機目錄,就不存在強耦合。如果宿主機目錄發生改變,只要改變數據卷與宿主機目錄之間的映射關系即可。
不過,我們通過由于數據卷目錄比較深,不好尋找,通常我們也允許讓容器直接與宿主機目錄掛載而不使用數據卷,具體參考2.2.3小節。
2.2.2.數據卷命令
數據卷的相關命令有:
注意:容器與數據卷的掛載要在創建容器時配置,對于創建好的容器,是不能設置數據卷的。而且創建容器的過程中,數據卷會自動創建。
教學演示環節:演示一下nginx的html
目錄掛載
# 1.首先創建容器并指定數據卷,注意通過 -v 參數來指定數據卷
docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx# 2.然后查看數據卷
docker volume ls
# 結果
DRIVER VOLUME NAME
local 29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f
local html# 3.查看數據卷詳情
docker volume inspect html
# 結果
[{"CreatedAt": "2024-05-17T19:57:08+08:00","Driver": "local","Labels": null,"Mountpoint": "/var/lib/docker/volumes/html/_data","Name": "html","Options": null,"Scope": "local"}
]# 4.查看/var/lib/docker/volumes/html/_data目錄
ll /var/lib/docker/volumes/html/_data
# 可以看到與nginx的html目錄內容一樣,結果如下:
總用量 8
-rw-r--r--. 1 root root 497 12月 28 2021 50x.html
-rw-r--r--. 1 root root 615 12月 28 2021 index.html# 5.進入該目錄,并隨意修改index.html內容
cd /var/lib/docker/volumes/html/_data
vi index.html# 6.打開頁面,查看效果# 7.進入容器內部,查看/usr/share/nginx/html目錄內的文件是否變化
docker exec -it nginx bash
教學演示環節:演示一下MySQL的匿名數據卷
# 1.查看MySQL容器詳細信息
docker inspect mysql
# 關注其中.Config.Volumes部分和.Mounts部分
我們關注兩部分內容,第一是.Config.Volumes部分:
{"Config": {// ... 略"Volumes": {"/var/lib/mysql": {}}// ... 略}
}
可以發現這個容器聲明了一個本地目錄,需要掛載數據卷,但是數據卷未定義。這就是匿名卷。然后,我們再看結果中的.Mounts
部分:
{"Mounts": [{"Type": "volume","Name": "29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f","Source": "/var/lib/docker/volumes/29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f/_data","Destination": "/var/lib/mysql","Driver": "local",}]
}
可以發現,其中有幾個關鍵屬性:
- Name:數據卷名稱。由于定義容器未設置容器名,這里的就是匿名卷自動生成的名字,一串hash值。
- Source:宿主機目錄
- Destination : 容器內的目錄
上述配置是將容器內的/var/lib/mysql
這個目錄,與數據卷
29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f
掛載。于是在宿主機中就有了/var/lib/docker/volumes/29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f/_data
這個目錄。這就是匿名數據卷對應的目錄,其使用方式與普通數據卷沒有差別。
接下來,可以查看該目錄下的MySQL的data文件:
ls -l /var/lib/docker/volumes/29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f/_data
注意:每一個不同的鏡像,將來創建容器后內部有哪些目錄可以掛載,可以參考DockerHub對應的頁面
2.2.3.掛載本地目錄或文件
可以發現,數據卷的目錄結構較深,如果我們去操作數據卷目錄會不太方便。在很多情況下,我們會直接將容器目錄與宿主機指定目錄掛載。掛載語法與數據卷類似:
# 掛載本地目錄
-v 本地目錄:容器內目錄
# 掛載本地文件
-v 本地文件:容器內文件
注意:本地目錄或文件必須以 /
或 .
/開頭,如果直接以名字開頭,會被識別為數據卷名而非本地目錄名。
例如:
-v mysql:/var/lib/mysql # 會被識別為一個數據卷叫mysql,運行時會自動創建這個數據卷
-v ./mysql:/var/lib/mysql # 會被識別為當前目錄下的mysql目錄,運行時如果不存在會創建目錄
教學演示,刪除并重新創建mysql容器,并完成本地目錄掛載:
- 掛載
/root/mysql/data
到容器內的/var/lib/mysql
目錄 - 掛載
/root/mysql/init
到容器內的/docker-entrypoint-initdb.d
目錄(初始化的SQL腳本目錄) - 掛載
/root/mysql/conf
到容器內的/etc/mysql/conf.d
目錄(這個是MySQL配置文件目錄)
在資料中已經準備好了mysql的init
目錄和 conf
目錄:
以及對應的初始化SQL腳本和配置文件:
其中,hm.cnf主要是配置了MySQL的默認編碼,改為utf8mb4;而hmall.sql則是后面我們要用到的黑馬商城項目的初始化SQL腳本。
我們直接將整個mysql目錄上傳至虛擬機的/root目錄下:
接下來,我們演示本地目錄掛載:
# 1.刪除原來的MySQL容器
docker rm -f mysql# 2.進入root目錄
cd ~# 3.創建并運行新mysql容器,掛載本地目錄
docker run -d \--name mysql \-p 3306:3306 \-e TZ=Asia/Shanghai \-e MYSQL_ROOT_PASSWORD=123 \-v ./mysql/data:/var/lib/mysql \-v ./mysql/conf:/etc/mysql/conf.d \-v ./mysql/init:/docker-entrypoint-initdb.d \mysql# 4.查看root目錄,可以發現~/mysql/data目錄已經自動創建好了
ls -l mysql
# 結果:
總用量 4
drwxr-xr-x. 2 root root 20 5月 19 15:11 conf
drwxr-xr-x. 7 polkitd root 4096 5月 19 15:11 data
drwxr-xr-x. 2 root root 23 5月 19 15:11 init# 查看data目錄,會發現里面有大量數據庫數據,說明數據庫完成了初始化
ls -l data# 5.查看MySQL容器內數據
# 5.1.進入MySQL
docker exec -it mysql mysql -uroot -p123
# 5.2.查看編碼表
show variables like "%char%";
# 5.3.結果,發現編碼是utf8mb4沒有問題
+--------------------------+--------------------------------+
| Variable_name | Value |
+--------------------------+--------------------------------+
| character_set_client | utf8mb4 |
| character_set_connection | utf8mb4 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8mb4 |
| character_set_server | utf8mb4 |
| character_set_system | utf8mb3 |
| character_sets_dir | /usr/share/mysql-8.0/charsets/ |
+--------------------------+--------------------------------+# 6.查看數據
# 6.1.查看數據庫
show databases;
# 結果,hmall是黑馬商城數據庫
+--------------------+
| Database |
+--------------------+
| hmall |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
# 6.2.切換到hmall數據庫
use hmall;
# 6.3.查看表
show tables;
# 結果:
+-----------------+
| Tables_in_hmall |
+-----------------+
| address |
| cart |
| item |
| order |
| order_detail |
| order_logistics |
| pay_order |
| user |
+-----------------+
# 6.4.查看address表數據
+----+---------+----------+--------+----------+-------------+---------------+-----------+------------+-------+
| id | user_id | province | city | town | mobile | street | contact | is_default | notes |
+----+---------+----------+--------+----------+-------------+---------------+-----------+------------+-------+
| 59 | 1 | 北京 | 北京 | 朝陽區 | 13900112222 | 金燕龍辦公樓 | 李佳誠 | 0 | NULL |
| 60 | 1 | 北京 | 北京 | 朝陽區 | 13700221122 | 修正大廈 | 李佳紅 | 0 | NULL |
| 61 | 1 | 上海 | 上海 | 浦東新區 | 13301212233 | 航頭鎮航頭路 | 李佳星 | 1 | NULL |
| 63 | 1 | 廣東 | 佛山 | 永春 | 13301212233 | 永春武館 | 李曉龍 | 0 | NULL |
+----+---------+----------+--------+----------+-------------+---------------+-----------+------------+-------+
4 rows in set (0.00 sec)
2.3.鏡像
前面我們一直在使用別人準備好的鏡像,那如果我要部署一個Java項目,把它打包為一個鏡像該怎么做呢?
2.3.1.鏡像結構
要想自己構建鏡像,必須先了解鏡像的結構。
之前我們說過,鏡像之所以能讓我們快速跨操作系統部署應用而忽略其運行環境、配置,就是因為鏡像中包含了程序運行需要的系統函數庫、環境、配置、依賴。
因此,自定義鏡像本質就是依次準備好程序運行的基礎環境、依賴、應用本身、運行配置等文件,并且打包而成。
舉個例子,我們要從0部署一個Java應用,大概流程是這樣:
- 準備一個linux服務(
CentOS
或者Ubuntu
均可) - 安裝并配置JDK
- 上傳Jar包
- 運行jar包
那因此,我們打包鏡像也是分成這么幾步:
- 準備Linux運行環境(java項目并不需要完整的操作系統,僅僅是基礎運行環境即可)
- 安裝并配置JDK
- 拷貝jar包
- 配置啟動腳本
上述步驟中的每一次操作其實都是在生產一些文件(系統運行環境、函數庫、配置最終都是磁盤文件),所以鏡像就是一堆文件的集合。
但需要注意的是,鏡像文件不是隨意堆放的,而是按照操作的步驟分層疊加而成,每一層形成的文件都會單獨打包并標記一個唯一id,稱為Layer
(層)。這樣,如果我們構建時用到的某些層其他人已經制作過,就可以直接拷貝使用這些層,而不用重復制作。
例如,第一步中需要的Linux運行環境,通用性就很強,所以Docker官方就制作了這樣的只包含Linux運行環境的鏡像。我們在制作java鏡像時,就無需重復制作,直接使用Docker官方提供的CentOS或Ubuntu鏡像作為基礎鏡像。然后再搭建其它層即可,這樣逐層搭建,最終整個Java項目的鏡像結構如圖所示:
2.3.2.Dockerfile
由于制作鏡像的過程中,需要逐層處理和打包,比較復雜,所以Docker就提供了自動打包鏡像的功能。我們只需要將打包的過程,每一層要做的事情用固定的語法寫下來,交給Docker去執行即可。
而這種記錄鏡像結構的文件就稱為Dockerfile
,其對應的語法可以參考官方文檔:
https://docs.docker.com/engine/reference/builder/
其中的語法比較多,比較常用的有:
例如,要基于Ubuntu鏡像來構建一個Java應用,其Dockerfile內容如下:
# 指定基礎鏡像
FROM ubuntu:16.04
# 配置環境變量,JDK的安裝目錄、容器內時區
ENV JAVA_DIR=/usr/local
ENV TZ=Asia/Shanghai
# 拷貝jdk和java項目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar
# 設定時區
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 安裝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
# 指定項目監聽的端口
EXPOSE 8080
# 入口,java項目的啟動命令
ENTRYPOINT ["java", "-jar", "/app.jar"]
同學們思考一下:以后我們會有很多很多java項目需要打包為鏡像,他們都需要Linux系統環境、JDK環境這兩層,只有上面的3層不同(因為jar包不同)。如果每次制作java鏡像都重復制作前兩層鏡像,是不是很麻煩。
所以,就有人提供了基礎的系統加JDK環境,我們在此基礎上制作java鏡像,就可以省去JDK的配置了:
# 基礎鏡像
FROM openjdk:11.0-jre-buster
# 設定時區
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷貝jar包
COPY docker-demo.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
是不是簡單多了。
2.3.3.構建鏡像
當Dockerfile文件寫好以后,就可以利用命令來構建鏡像了。
在資料中,我們準備好了一個demo項目及對應的Dockerfile:
首先,我們將課前資料提供的docker-demo.jar包以及Dockerfile拷貝到虛擬機的/root/demo目錄:
然后,執行命令,構建鏡像:
# 進入鏡像目錄
cd /root/demo
# 開始構建
docker build -t docker-demo:1.0 .
命令說明:
docker build
: 就是構建一個docker鏡像-t docker-demo:1.0
:-t
參數是指定鏡像的名稱(repository
和tag
).
: 最后的點是指構建時Dockerfile
所在路徑,由于我們進入了demo目錄,所以指定的是.代表當前目錄,也可以直接指定Dockerfile
目錄:
# 直接指定Dockerfile目錄
docker build -t docker-demo:1.0 /root/demo
結果:
查看鏡像列表:
#查看鏡像docker images
#結果
REPOSITORY TAG IMAGE ID CREATED SIZE
doker-demo latest cd28ba0789d8 About a minute ago 315MB
redis latest 860da63e75fb 12 days ago 128MB
nginx latest be69f2940aaf 7 weeks ago 192MB
mysql latest edbdd97bf78b 8 weeks ago 859MB
mysql 8.0 43006ac274fd 8 weeks ago 772MB
openjdk 11.0-jre-buster 0b489110c503 2 years ago 297MB
然后嘗試運行該鏡像:
# 1.創建并運行容器
docker run -d --name dd -p 8080:8080 docker-demo:1.0
# 2.查看容器
dps
# 結果
CONTAINER ID IMAGE PORTS STATUS NAMES
78a000447b49 docker-demo:1.0 0.0.0.0:8080->8080/tcp, :::8090->8090/tcp Up 2 seconds dd
f63cfead8502 mysql 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp Up 2 hours mysql# 3.訪問
curl localhost:8080/hello/count
# 結果:
<h5>歡迎訪問黑馬商城, 這是您第1次訪問<h5>
2.4.網絡
默認情況下,所有容易都是以bridge
的方式連接到Docker的一個虛擬網橋上:
上節課我們創建了一個Java項目的容器,而Java項目往往需要訪問其它各種中間件,例如MySQL、Redis等。現在,我們的容器之間能否互相訪問呢?我們來測試一下
首先,我們查看下MySQL容器的詳細信息,重點關注其中的網絡IP地址:
# 1.用基本命令,尋找Networks.bridge.IPAddress屬性
docker inspect mysql
# 也可以使用format過濾結果
docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' mysql
# 得到IP地址如下:
172.17.0.2# 2.然后通過命令進入dd容器
docker exec -it dd bash# 3.在容器內,通過ping命令測試網絡
ping 172.17.0.2
# 結果
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.053 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.059 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.058 ms
發現可以互聯,沒有問題。
但是,容器的網絡IP其實是一個虛擬的IP,其值并不固定與某一個容器綁定,如果我們在開發時寫死某個IP,而在部署時很可能MySQL容器的IP會發生變化,連接會失敗。
所以,我們必須借助于docker的網絡功能來解決這個問題,官方文檔:
https://docs.docker.com/engine/reference/commandline/network/
常見命令有:
教學演示:自定義網絡
# 1.首先通過命令創建一個網絡
docker network create hmall# 2.然后查看網絡
docker network ls
# 結果:
NETWORK ID NAME DRIVER SCOPE
639bc44d0a87 bridge bridge local
403f16ec62a2 hmall bridge local
0dc0f72a0fbb host host local
cd8d3e8df47b none null local
# 其中,除了hmall以外,其它都是默認的網絡# 3.讓dd和mysql都加入該網絡,注意,在加入網絡時可以通過--alias給容器起別名
# 這樣該網絡內的其它容器可以用別名互相訪問!
# 3.1.mysql容器,指定別名為db,另外每一個容器都有一個別名是容器名
docker network connect hmall mysql --alias db
# 3.2.db容器,也就是我們的java項目
docker network connect hmall dd# 4.進入dd容器,嘗試利用別名訪問db
# 4.1.進入容器
docker exec -it dd bash
# 4.2.用db別名訪問
ping db
# 結果
PING db (172.18.0.2) 56(84) bytes of data.
64 bytes from mysql.hmall (172.18.0.2): icmp_seq=1 ttl=64 time=0.070 ms
64 bytes from mysql.hmall (172.18.0.2): icmp_seq=2 ttl=64 time=0.056 ms
# 4.3.用容器名訪問
ping mysql
# 結果:
PING mysql (172.18.0.2) 56(84) bytes of data.
64 bytes from mysql.hmall (172.18.0.2): icmp_seq=1 ttl=64 time=0.044 ms
64 bytes from mysql.hmall (172.18.0.2): icmp_seq=2 ttl=64 time=0.054 ms
# 實現自定義同一網橋內的容器之間就可以相互訪問了
OK,現在無需記住IP地址也可以實現容器互聯了。
總結:
- 在自定義網絡中,可以給容器起多個別名,默認的別名是容器名本身
- 在同一個自定義網絡中的容器,可以通過別名互相訪問
3.項目部署
好了,我們已經熟悉了Docker的基本用法,接下來可以嘗試部署項目了。
在課前資料中已經提供了一個黑馬商城項目給大家,如圖:
項目說明:
- hmall:商城的后端代碼
- hmall-portal:商城用戶端的前端代碼
- hmall-admin:商城管理端的前端代碼
部署的容器及端口說明:
在正式部署前,我們先刪除之前的nginx、dd兩個容器:
docker rm -f nginx dd
mysql容器中已經準備好了商城的數據,所以就不再刪除了。
3.1.部署Java項目
hmall項目是一個maven聚合項目,使用IDEA打開hmall項目,查看項目結構如圖:
我們要部署的就是其中的hm-service,其中的配置文件采用了多環境的方式:
其中的application-dev.yaml是部署到開發環境的配置,application-local.yaml是本地運行時的配置。
查看application.yaml,你會發現其中的JDBC地址并未寫死,而是讀取變量:
這兩個變量在application-dev.yaml和application-local.yaml中并不相同:
在dev開發環境(也就是Docker部署時)采用了mysql作為地址,剛好是我們的mysql容器名,只要兩者在一個網絡,就一定能互相訪問。
我們將項目打包:
結果:
將hm-service目錄下的Dockerfile和hm-service/target目錄下的hm-service.jar一起上傳到虛擬機的root目錄:
部署項目:
# 1.構建項目鏡像,不指定tag,則默認為latest
docker build -t hmall .# 2.查看鏡像
docker images
# 結果
REPOSITORY TAG IMAGE ID CREATED SIZE
hmall latest 0bb07b2c34b9 43 seconds ago 362MB
docker-demo 1.0 49743484da68 24 hours ago 327MB
nginx latest 605c77e624dd 16 months ago 141MB
mysql latest 3218b38490ce 17 months ago 516MB# 3.創建并運行容器,并通過--network將其加入hmall網絡,這樣才能通過容器名訪問mysql
docker run -d --name hmall --network hmall -p 8080:8080 hmall
測試,通過瀏覽器訪問:http://你的虛擬機地址:8080/search/list
3.2.部署前端
hmall-portal
和hmall-admin
是前端代碼,需要基于nginx部署。在課前資料中已經給大家提供了nginx的部署目錄:
其中:
html
是靜態資源目錄,我們需要把hmall-portal
以及hmall-admin
都復制進去nginx.conf
是nginx的配置文件,主要是完成對html
下的兩個靜態資源目錄做代理
我們現在要做的就是把整個nginx目錄上傳到虛擬機的/root
目錄下:
然后創建nginx容器并完成兩個掛載:
- 把
/root/nginx/nginx.conf
掛載到/etc/nginx/nginx.conf
- 把
/root/nginx/html
掛載到/usr/share/nginx/html
由于需要讓nginx
同時代理hmall-portal
和hmall-admin
兩套前端資源,因此我們需要暴露兩個端口:
- 18080:對應
hmall-portal
- 18081:對應
hmall-admin
命令如下:
docker run -d \--name nginx \-p 18080:18080 \-p 18081:18081 \-v /root/nginx/html:/usr/share/nginx/html \-v /root/nginx/nginx.conf:/etc/nginx/nginx.conf \--network lk_xiao_du \nginx
測試,通過瀏覽器訪問:http://你的虛擬機ip
:18080
3.3.DockerCompose
大家可以看到,我們部署一個簡單的java項目,其中包含3個容器:
- MySQL
- Nginx
- Java項目
而稍微復雜的項目,其中還會有各種各樣的其它中間件,需要部署的東西遠不止3個。如果還像之前那樣手動的逐一部署,就太麻煩了。
而Docker Compose
就可以幫助我們實現多個相互關聯的Docker容器的快速部署。它允許用戶通過一個單獨的docker-compose.yml
模板文件(YAML 格式)來定義一組相關聯的應用容器。
3.3.1.基本語法
docker-compose.yml文件的基本語法可以參考官方文檔:
https://docs.docker.com/compose/compose-file/compose-file-v3/
docker-compose文件中可以定義多個相互關聯的應用容器,每一個應用容器被稱為一個服務(service)。由于service就是在定義某個應用的運行時參數,因此與docker run參數非常相似。
舉例來說,用docker run部署MySQL的命令如下:
docker run -d \--name mysql \-p 3306:3306 \-e TZ=Asia/Shanghai \-e MYSQL_ROOT_PASSWORD=123 \-v ./mysql/data:/var/lib/mysql \-v ./mysql/conf:/etc/mysql/conf.d \-v ./mysql/init:/docker-entrypoint-initdb.d \--network hmallmysql
如果用docker-compose.yml
文件來定義,就是這樣:
version: "3.8"
services:mysql:image: mysqlcontainer_name: mysqlports:- "3306:3306"environment:TZ: Asia/ShanghaiMYSQL_ROOT_PASSWORD: 123volumes:- "./mysql/conf:/etc/mysql/conf.d"- "./mysql/data:/var/lib/mysql"networks:- new
networks:new:name: hmall
對比如下:
明白了其中的對應關系,相信編寫docker-compose文件應該難不倒大家。
黑馬商城部署文件:
version: "3.8"services:mysql:image: mysqlcontainer_name: mysqlports:- "3306:3306"environment:TZ: Asia/ShanghaiMYSQL_ROOT_PASSWORD: 123volumes:- "./mysql/conf:/etc/mysql/conf.d"- "./mysql/data:/var/lib/mysql"- "./mysql/init:/docker-entrypoint-initdb.d"networks:- hm-nethmall:build: context: .dockerfile: Dockerfilecontainer_name: hmallports:- "8080:8080"networks:- hm-netdepends_on:- mysqlnginx:image: nginxcontainer_name: nginxports:- "18080:18080"- "18081:18081"volumes:- "./nginx/nginx.conf:/etc/nginx/nginx.conf"- "./nginx/html:/usr/share/nginx/html"depends_on:- hmallnetworks:- hm-net
networks:hm-net:name: hmall
3.3.2.基礎命令
編寫好docker-compose.yml文件,就可以部署項目了。常見的命令:
https://docs.docker.com/compose/reference/
基本語法如下:
docker compose [OPTIONS] [COMMAND]
其中,OPTIONS和COMMAND都是可選參數,比較常見的有:
教學演示:
# 1.進入root目錄
cd /root# 2.刪除舊容器
docker rm -f $(docker ps -qa)# 3.刪除hmall鏡像
docker rmi hmall# 4.清空MySQL數據
rm -rf mysql/data# 5.啟動所有, -d 參數是后臺啟動
docker compose up -d
# 結果:
[+] Building 15.5s (8/8) FINISHED=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 358B 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/openjdk:11.0-jre-buster 15.4s=> [1/3] FROM docker.io/library/openjdk:11.0-jre-buster@sha256:3546a17e6fb4ff4fa681c3 0.0s=> [internal] load build context 0.0s=> => transferring context: 98B 0.0s=> CACHED [2/3] RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 0.0s=> CACHED [3/3] COPY hm-service.jar /app.jar 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:32eebee16acde22550232f2eb80c69d2ce813ed099640e4cfed2193f71 0.0s=> => naming to docker.io/library/root-hmall 0.0s
[+] Running 4/4? Network hmall Created 0.2s? Container mysql Started 0.5s? Container hmall Started 0.9s? Container nginx Started 1.5s# 6.查看鏡像
docker compose images
# 結果
CONTAINER REPOSITORY TAG IMAGE ID SIZE
hmall root-hmall latest 32eebee16acd 362MB
mysql mysql latest 3218b38490ce 516MB
nginx nginx latest 605c77e624dd 141MB# 7.查看容器
docker compose ps
# 結果
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
hmall root-hmall "java -jar /app.jar" hmall 54 seconds ago Up 52 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp
mysql mysql "docker-entrypoint.s…" mysql 54 seconds ago Up 53 seconds 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp
nginx nginx "/docker-entrypoint.…" nginx 54 seconds ago Up 52 seconds 80/tcp, 0.0.0.0:18080-18081->18080-18081/tcp, :::18080-18081->18080-18081/tcp
打開瀏覽器,訪問:http://虛擬機IP地址
:8080