Docker是基於Go語言開發的開源應用容器引擎,遵從Apache Licence 2.0協議,可以讓開發者打包應用以及應用的依賴包到一個可移植的容器中,然後發佈到各種發行版本的Linux系統上。
軟件開發中最爲麻煩的事情可能就是配置環境了。由於用戶使用的操作系統具有多樣性,即便使用跨平臺的開發語言(如Java和Python)都不能保證代碼能夠在各種平臺下都可以正常的運轉,而且在不同的環境下我們安裝的軟件需要依賴的軟件包也是不一樣的。
那麼問題來了,我們安裝軟件的時候可不可以把軟件運行的環境一併安裝?我們是不是可以把原始環境一模一樣地複製過來呢?
虛擬機(virtual machine)就是帶環境安裝的一種解決方案,它可以在一種操作系統裏面運行另一種操作系統,比如在Windows系統裏面運行Linux系統,在macOS上運行Windows,而應用程序對此毫無感知。使用過虛擬機的人都知道,虛擬機用起來跟真實系統一模一樣,而對於虛擬機的宿主系統來說,虛擬機就是一個普通文件,不需要了就刪掉,對宿主系統或者其他的程序並沒有影響。但是虛擬機通常會佔用較多的系統資源,啓動和關閉也非常的緩慢,總之用戶體驗並沒有想象中的那麼好。
Docker屬於對Linux容器技術(LXC)的一種封裝(利用了Linux的namespace和cgroup技術),它提供了簡單易用的容器使用接口,是目前最流行的 Linux 容器解決方案。Docker將應用程序與該程序的依賴打包在一個文件裏面,運行這個文件,就會生成一個虛擬容器。程序在這個虛擬容器裏運行,就好像在真實的物理機上運行一樣。下圖是虛擬機和容器的對比,左邊是傳統的虛擬機,右邊是Docker。
目前,Docker主要用於幾下幾個方面:
- 提供一次性的環境。
- 提供彈性的雲服務(利用Docker很容易實現擴容和收縮)。
- 實踐微服務架構(隔離真實環境在容器中運行多個服務)。
下面以CentOS爲例講解如何安裝Docker,使用Ubuntu、macOS或Windows的用戶可以通過點擊對應的鏈接瞭解這些平臺下如何進行安裝。
-
確定操作系統內核版本(CentOS 7要求64位,內核版本3.10+;CentOS 6要求64位,內核版本2.6+)。
uname -r
-
更新系統底層的庫文件(建議一定要執行,否則在使用Docker時可能會出現莫名其妙的問題)。
yum update
-
移除可能存在的舊的Docker版本。
yum erase -y docker docker-common docker-engine
-
安裝yum工具包和依賴項。
yum install -y yum-utils device-mapper-persistent-data lvm2
-
通過yum工具包添加yum源(安裝Docker-ce的源)。
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
-
在CentOS下使用yum安裝Docker-ce並啓動。
yum -y install docker-ce systemctl start docker
-
查看Docker的信息和版本。
docker version docker info
接下來可以通過下載鏡像和創建容器來看看Docker是否可以運轉起來。可以使用下面的命令從Docker的鏡像倉庫下載名爲hello-world的鏡像文件。
docker pull hello-world
查看所有鏡像文件。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/hello-world latest fce289e99eb9 7 months ago 1.84 kB
通過鏡像文件創建並運行容器。
docker container run --name mycontainer hello-world
說明:其中
mycontainer
是我們給容器起的名字,跟在--name
參數之後;hello-world
就是我們剛纔下載的鏡像文件。
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
如果要刪除這個容器,可以使用下面的命令。
docker container rm mycontainer
在刪除容器之後,我們還可以刪除剛纔下載的鏡像文件。
docker rmi hello-world
說明:如果要在Ubuntu(內核版本3.10+)下面安裝和啓動Docker,可以按照如下的步驟進行。
apt update apt install docker-ce service docker start國內用戶可以通過更換Ubuntu軟件下載源來提升下載速度,具體請參照清華大學開源軟件鏡像站上的《Ubuntu鏡像使用幫助》。
安裝Docker後,由於直接訪問dockerhub下載鏡像會非常緩慢,建議將服務器更換爲國內鏡像,可以通過修改 /etc/docker/daemon.json
文件來做到。一般的雲服務器會有自己專屬的鏡像,就不需要手動修改了。
{
"registry-mirrors": [
"http://hub-mirror.c.163.com",
"https://registry.docker-cn.com"
]
}
想要玩轉Docker,最簡單的辦法就是馬上用Docker創建一些自己學習和工作中需要用到的容器,下面我們帶着大家一起來創建這些容器。
Nginx是高性能的Web服務器,同時也是做反向代理服務器的上佳選擇。使用Docker可以非常簡單的創建一個運行Nginx的容器,命令如下所示。
docker container run -d -p 80:80 --rm --name mynginx nginx
說明:上面的參數
-d
表示容器在後臺運行(不產生輸出到Shell)並顯示容器的ID;-p
是用來映射容器的端口到宿主機的端口,冒號前面是宿主機的端口,冒號後面是容器內部使用的端口;--rm
表示容器停止後自動刪除容器,例如執行命令docker container stop mynginx
後,容器就不復存在了;--name
後面的mynginx是自定義的容器名字;在創建容器的過程中,需要用到nginx的鏡像文件,鏡像文件的下載是自動完成的,如果沒有指定版本號,默認是最新版本(latest)。
如果需要將自己的Web項目(頁面)部署到Nginx上,可以使用容器拷貝命令將指定路徑下所有的文件和文件夾拷貝到容器的指定目錄中。
docker container cp /root/web/index.html mynginx:/usr/share/nginx/html
如果不願意拷貝文件也可以在創建容器時通過數據卷操作--volume
將指定的文件夾映射到容器的某個目錄中,例如將Web項目的文件夾直接映射到/usr/share/nginx/html
目錄。我們先通過下面的命令讓剛纔創建的容器停止運行。
docker container stop mynginx
然後用下面的命令重新創建容器。
docker container run -d -p 80:80 --rm --name mynginx --volume /root/docker/nginx/html:/usr/share/nginx/html nginx
說明:上面創建容器和拷貝文件的命令中,
container
是可以省略的,也就是說docker container run
和docker run
是一樣的,而docker container cp
和docker cp
是一樣的。此外,命令中的--volume
也可以縮寫爲-v
,就如同-d
是--detach
的縮寫,-p
是--publish
的縮寫。$PWD
代表宿主系統當前文件夾,這些對於使用過Unix或者Linux系統的人來說,應該是很容易理解的。
要查看運行中的容器,可以使用下面的命令。
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c38d2476384 nginx "nginx -g 'daemon ..." 4 seconds ago Up 4 seconds 0.0.0.0:80->80/tcp mynginx
要啓動和停止容器,可以使用下面的命令。
docker start mynginx
docker stop mynginx
由於在創建容器時使用了--rm
選項,容器在停止時會被移除,當我們使用下面的命令查看所有容器時,應該已經看不到剛纔的mynginx
容器了。
docker container ls -a
如果在創建容器時沒有指定--rm
選項,那麼也可以使用下面的命令來刪除容器。
docker rm mynginx
要刪除正在運行中的容器,需要使用-f
選項。
docker rm -f mynginx
我們再來嘗試用Docker安裝一臺MySQL服務器,首先可以先檢查一下有沒有MySQL的鏡像文件。
docker search mysql
INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED
docker.io docker.io/mysql MySQL is a ... 8486 [OK]
...
說明:上面查詢結果的列依次代表索引、鏡像名、鏡像描述、用戶評價、是否官方鏡像、自動構建。
下載MySQL鏡像並指定鏡像的版本號。
docker pull mysql:5.7
如果需要查看已經下載的鏡像文件,可以使用下面的命令。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/nginx latest e445ab08b2be 2 weeks ago 126 MB
docker.io/mysql 5.7 f6509bac4980 3 weeks ago 373 MB
創建並運行MySQL容器。
docker run -d -p 3306:3306 --name mysql57 -v /root/docker/mysql/conf:/etc/mysql/mysql.conf.d -v /root/docker/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
注意:上面創建容器時我們又一次使用了數據卷操作,那是因爲通常容器是隨時創建隨時刪除的,而數據庫中的數據卻是需要保留下來的。
上面的兩個數據卷操作一個是映射了MySQL配置文件所在的文件夾,一個是映射了MySQL數據所在的文件夾,這兩個數據卷操作非常重要。我們可以將MySQL的配置文件放在$PWD/mysql/conf
目錄下,配置文件的具體內容如下所示:
[mysqld]
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/run/mysqld/mysqld.sock
datadir=/var/lib/mysql
log-error=/var/log/mysql/error.log
server-id=1
log-bin=/var/log/mysql/mysql-bin.log
expire_logs_days=30
max_binlog_size=256M
symbolic-links=0
如果安裝了MySQL 8.x版本(目前的最新版本),在使用客戶端工具連接服務器時可能會遇到error 2059: Authentication plugin 'caching_sha2_password' cannot be loaded
的問題,這是因爲MySQL 8.x默認使用了名爲“caching_sha2_password”的機制對用戶口令進行了更好的保護,但是如果客戶端工具不支持新的認證方式,連接就會失敗。解決這個問題有兩種方式:一是升級客戶端工具來支持MySQL 8.x的認證方式;二是進入容器,修改MySQL的用戶口令認證方式。下面是具體的步驟,我們先用docker exec
命令進入容器的交互式環境,假設運行MySQL 8.x的容器名字叫mysql8x
。
docker exec -it mysql8x /bin/bash
進入容器的交互式Shell之後,可以首先利用MySQL的客戶端工具連接MySQL服務器。
mysql -u root -p
Enter password:
Your MySQL connection id is 16
Server version: 8.0.12 MySQL Community Server - GPL
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
接下來通過SQL來修改用戶口令就可以了。
alter user 'root'@'%' identified with mysql_native_password by '123456' password expire never;
當然,如果願意你也可以查看一下用戶表檢查是否修改成功。
use mysql;
select user, host, plugin, authentication_string from user where user='root';
+------+-----------+-----------------------+-------------------------------------------+
| user | host | plugin | authentication_string |
+------+-----------+-----------------------+-------------------------------------------+
| root | % | mysql_native_password | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
| root | localhost | mysql_native_password | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
+------+-----------+-----------------------+-------------------------------------------+
2 rows in set (0.00 sec)
在完成上面的步驟後,現在即便不更新客戶端工具也可以連接MySQL 8.x了。
接下來我們試一試運行多個容器並讓多個容器之間通過網絡通信。我們創建4個Redis容器來實現一主三從的主從複製結構。
docker run -d -p 6379:6379 --name redis-master redis
docker run -d -p 6380:6379 --name redis-slave-1 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
docker run -d -p 6381:6379 --name redis-slave-2 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
docker run -d -p 6382:6379 --name redis-slave-3 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
上面的命令中,--link
參數用於給容器創建網絡別名,因爲三臺從機(slave)需要通過網絡連接自己的主機(master)。雖然,我們可以通過docker inspect --format '{{ .NetworkSettings.IPAddress }}' <container-ID>
命令來查看到容器的IP地址,但是由於容器的即裝即用性,容器的IP地址有可能會發生變化,如果直接使用IP地址,在容器重啓後就可能會因爲IP地址的變化導致從機無法連接到主機。使用--link
參數創建網絡別名就是爲了在啓動Redis服務器時在redis-server
後面的--replicaof
參數後使用這個別名而不是IP地址。
接下來我們進入名爲redis-master
的容器,看看主從複製的配置是否成功。
docker exec -it redis-master /bin/bash
通過redis-cli
啓動命令行工具。
redis-cli
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:3
slave0:ip=172.17.0.4,port=6379,state=online,offset=1988,lag=0
slave1:ip=172.17.0.5,port=6379,state=online,offset=1988,lag=1
slave2:ip=172.17.0.6,port=6379,state=online,offset=1988,lag=1
master_replid:94703cfa03c3ddc7decc74ca5b8dd13cb8b113ea
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1988
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1988
GitLab是由GitLab Inc.開發的Git倉庫管理工具,具有wiki、問題跟蹤、持續集成等一系列的功能,分爲社區版和企業版。通過Docker提供的虛擬化容器,我們可以安裝社區版的Docker。因爲GitLab需要使用SSH協議進行安全連接,我們要暴露容器的22端口,所以可以先將宿主機SSH連接的22端口修改爲其他端口(如:12345),然後再進行後續的操作。
vim /etc/ssh/sshd_config
將其中定義端口的那行代碼去掉註釋並將端口修改爲12345。
Port 12345
重新啓動sshd
服務。
systemctl restart sshd
提示:修改端口後應該確保防火牆上也開啓對應的端口,否則無法使用SSH連接到Linux服務器。
創建需要用於數據卷映射操作的文件夾。
mkdir -p /root/gitlab/{config,logs,data}
基於gitlab/gitlab-ce
鏡像創建容器,並暴露80端口(HTTP連接)和22端口(SSH連接)。
docker run -d -p 80:80 -p 22:22 --name gitlab -v /root/gitlab/config:/etc/gitlab -v /root/gitlab/logs:/var/log/gitlab -v /root/gitlab/data:/var/opt/gitlab gitlab/gitlab-ce
說明:GitLab的啓動比較緩慢,創建好容器後可能需要等待一段時間才能通過瀏覽器來進行訪問。
首次進入GitLab訪問界面會提示我們修改管理員密碼,設置好管理員密碼後就可以在登錄界面輸入用戶名root
和剛纔設置的密碼登錄到管理員控制檯,在使用上還是非常簡單和人性化的。
通過上面的講解,我們已經掌握瞭如何通過官方提供的鏡像來創建容器。當然如果願意,我們也可以用配置好的容器來生成鏡像。簡而言之,Docker鏡像是由文件系統疊加而成的,系統的最底層是bootfs,相當於就是Linux內核的引導文件系統;接下來第二層是rootfs,這一層可以是一種或多種操作系統(如Debian或Ubuntu文件系統),Docker中的rootfs是隻讀狀態的;Docker利用聯合掛載技術將各層文件系統疊加到一起,最終的文件系統會包含有底層的文件和目錄,這樣的文件系統就是一個鏡像。
之前我們講過了如何查找、列出鏡像和拉取(下載)鏡像,接下來看看構建鏡像的兩種方式:
- 使用
docker commit
命令。(不推薦) - 使用
docker build
命令和Dockerfile文件。
爲了演示如何構建鏡像,我們先使用Ubuntu鏡像來定製一個容器,命令如下所示。
docker run --name myubuntu -it ubuntu /bin/bash
在容器中執行下面的命令來安裝Apache服務器並退出容器。
apt -y upgrade
apt -y install apache2
exit
我們將這個容器作爲一個定製的Web服務器保存起來,當需要這樣一臺Web服務器的時候,就沒有必要重新創建容器並安裝Apache了。
首先我們通過下面的命令查看容器的ID。
docker container ls -a
docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
014bdb321612 ubuntu "/bin/bash" 5 minutes ago Exited (0) myubuntu
提交定製的容器。
docker commit 014bdb321612 jackfrued/mywebserver
查看鏡像文件。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jackfrued/mywebserver latest 795b294d265a 14 seconds ago 189 MB
生成鏡像文件以後,後面就可以利用剛纔創建的鏡像文件來創建新的容器。
Dockerfile使用DSL(Domain Specific Language)來構建一個Docker鏡像,只要編輯好了Dockerfile文件,就可以使用docker build
命令來構建一個新的鏡像。
我們先創建一個名爲myapp的文件夾來保存項目代碼和Dockerfile的文件,如下所示:
[ECS-root temp]# tree myapp
myapp
├── api
│ ├── app.py
│ ├── requirements.txt
│ └── start.sh
└── Dockerfile
其中api是Flask項目的文件夾,其中包括了項目代碼、依賴項以及啓動腳本等文件,具體內容如下所示:
app.py文件:
from flask import Flask
from flask_restful import Resource, Api
from flask_cors import CORS
app = Flask(__name__)
CORS(app, resources={r'/api/*': {'origins': '*'}})
api = Api(app)
class Product(Resource):
def get(self):
products = ['Ice Cream', 'Chocolate', 'Coca Cola', 'Hamburger']
return {'products': products}
api.add_resource(Product, '/api/products')
requirements.txt文件:
flask
flask-restful
flask-cors
gunicorn
start.sh文件:
#!/bin/bash
exec gunicorn -w 4 -b 0.0.0.0:8000 app:app
提示:需要給start.sh文件以執行權限,可以使用
chmod 755 start.sh
命令來做到。
Dockerfile文件:
# 指定基礎鏡像
FROM python:3.7
# 指定鏡像的維護者
MAINTAINER jackfrued "jackfrued@126.com"
# 將指定文件添加到容器中指定的位置
ADD api/* /root/api/
# 設置工作目錄
WORKDIR /root/api
# 執行命令(安裝Flask項目的依賴項)
RUN pip install -r requirements.txt -i https://pypi.doubanio.com/simple/
# 容器啓動時要執行的命令
ENTRYPOINT ["./start.sh"]
# 暴露端口
EXPOSE 8000
我們來解釋一下上面的Dockerfile文件。Dockerfile文件通過特殊的指令來指定基礎鏡像(FROM指令)、創建容器後需要指定的命令(RUN指令)以及需要暴露的端口(EXPOSE)等信息。我們稍後會專門爲大家介紹這些Dockfile中的指令。
接下來我們可以使用docker build
命令來創建鏡像,如下所示。
docker build -t "jackfrued/myapp" .
提示:上面的命令最後面的
.
千萬不要漏掉了哦,它表示從當前路徑下尋找Dockerfile。
通過下面的命令可以查看創建好的鏡像。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jackfrued/myapp latest 6d6f026a7896 5 seconds ago 930 MB
如果想知道鏡像文件是如何創建出來的,可以使用下面的命令。
docker history jackfrued/myapp
IMAGE CREATED CREATED BY SIZE COMMENT
6d6f026a7896 31 seconds ago /bin/sh -c #(nop) EXPOSE 8000/tcp 0 B
3f7739173a79 31 seconds ago /bin/sh -c #(nop) ENTRYPOINT ["./start.sh"] 0 B
321e6bf09bf1 32 seconds ago /bin/sh -c pip install -r requirements.txt... 13 MB
2f9bf2c89ac7 37 seconds ago /bin/sh -c #(nop) WORKDIR /root/api 0 B
86119afbe1f8 37 seconds ago /bin/sh -c #(nop) ADD multi:4b76f9c9dfaee8... 870 B
08d465e90d4d 3 hours ago /bin/sh -c #(nop) MAINTAINER jackfrued "j... 0 B
fbf9f709ca9f 12 days ago /bin/sh -c #(nop) CMD ["python3"] 0 B
使用該鏡像來創建容器運行Web服務器。
docker run -d -p 8000:8000 --name myapp jackfrued/myapp
如果希望將上面創建的鏡像文件放到dockerhub倉庫中,可以按照如下所示的步驟進行操作。
通過下面的命令登錄到dockerhub。
docker login
輸入用戶名和口令進行登錄。
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: jackfrued
Password:
Login Succeeded
通過下面的命令將鏡像推到倉庫中。
docker push jackfrued/webserver
想了解Dockerfile的指令可以查看官方提供的參考手冊,下面我們爲大家介紹一些常用的指令。
-
FROM:設置基礎鏡像,必須是Dockerfile中的第一條指令。
FROM <鏡像名> [AS <別名>]
或
FROM <鏡像名>[:<標籤>] [AS <別名>]
-
RUN:指定構建鏡像時要執行的命令。
RUN <命令> [參數1], [參數2], ...
或
RUN ["可執行文件", "參數1", "參數2", ...]
-
CMD:指定構建鏡像後要執行的命令。
CMD <命令> [參數1], [參數2], ...
或
CMD ["可執行文件", "參數1", "參數2", ...]
說明:Docker不同於虛擬機,容器本身就是一個進程,容器中的應用應該位於前臺運行。CMD命令相當於就是用來指定容器主進程(創建容器後要在前臺執行的程序)的,如果主進程結束了,容器也就停止運行了。所以在容器中啓動Nginx不能使用
service nginx start
或是systemctl start nginx
而是要通過CMD ["nginx", "-g", "daemon off;"]
讓它在前臺運行。 -
ENTRYPOINT:和CMD類似,也可以執行命令,但
docker run
命令行中指定的任何參數都會被當做參數再次傳給ENTRYPOINT指令中的命令,這就使得我們可以構建一個鏡像,它既可以運行一個默認的命令,也支持通過docker run
命令行爲該命令指定可覆蓋的參數選項。ENTRYPOINT <命令> [參數1], [參數2], ...
或
ENTRYPOINT ["可執行文件", "參數1", "參數2", ...]
-
WORKDIR:在通過鏡像創建新容器時,在容器內部創建一個工作目錄,ENTRYPOINT和CMD指定的程序會在這個目錄下執行。在使用
docker run
命令時可以通過-w
參數來覆蓋由WORKDIR指定的工作目錄。例如:WORKDIR /opt/webapp
docker run -w /usr/share/webapp ...
-
ENV:在創建鏡像時設置環境變量。在使用
docker run
命令時,可以通過-e
參數來修改環境變量的設置。例如:ENV DEFAULT_PORT=8080
docker run -e "DEFAULT_PORT=8000" ...
-
USER:指定鏡像會以什麼用戶身份去運行。例如:
USER nginx
-
VOLUME:在創建容器時添加一個數據卷的掛載點。通過數據卷操作可以實現容器間數據的共享和重用,對卷所作的修改可以馬上生效而不需要重新啓動容器,我們之前創建容器時使用
--volume
參數就是爲了實現數據卷的映射操作。VOLUME ["/路徑1", "/路徑2/子路徑2.1/", ...]
-
ADD:將構建目錄下的文件和文件夾複製到鏡像中,如果是壓縮文件和歸檔文件,ADD命令會對這些文件進行解壓縮解歸檔的操作。
ADD [--chown=<用戶>:<用戶組>] <源文件> <目標文件>
-
COPY:非常類似於ADD,但不會主動對文件進行提取操作。
-
LABEL:爲Docker鏡像添加一些元數據,在使用
docker inspect
命令時會看到這些元數據。LABEL version="1.0.0" location="Chengdu"
-
ONBUILD:爲鏡像添加觸發器,當一個鏡像被用作其他鏡像的基礎鏡像,觸發器將會被執行。例如:
ONBUILD ADD . /app/src ONBUILD RUN cd /app/src && make
我們的項目可能會使用了多個容器,容器多了之後管理容器的工作就會變得麻煩。如果要對多個容器進行自動配置使得容器可以相互協作甚至實現複雜的調度,這就需要進行容器編排。Docker原生對容器編排的支持非常弱,但是可以通過社區提供的工具來實現容器編排。
可以通過安裝Docker Compose工具來實現基於YAML文件的容器編排,YAML文件會定義一系列的容器以及容器運行時的屬性,Docker Compose會根據這些配置來管理容器。
-
安裝Docker Compose。
curl -L "https://github.com/docker/compose/releases/download/1.25.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose
說明:如果沒有curl工具,在CentOS下可以先通過包管理工具yum安裝curl再執行上面的命令。
當然我們也可以使用Python的包管理工具pip來安裝Docker Compose,命令如下所示。
pip3 install -U docker-compose
-
使用Docker Compose。
我們在剛纔的Flask項目中引入緩存,然後再利用Flask提供的數據接口爲前端頁面提供數據,使用Vue.js進行頁面渲染並將靜態頁面部署在Nginx服務器上。項目文件夾結構如下所示:
[ECS-root ~]# tree temp temp ├── docker-compose.yml ├── html │ └── index.html └── myapp ├── api │ ├── app.py │ ├── requirements.txt │ └── start.sh └── Dockerfile
修改後的app.py文件代碼如下所示:
from pickle import dumps, loads from flask import Flask from flask_restful import Resource, Api from flask_cors import CORS from redis import Redis app = Flask(__name__) CORS(app, resources={r'/api/*': {'origins': '*'}}) api = Api(app) redis = Redis(host='redis-master', port=6379) class Product(Resource): def get(self): data = redis.get('products') if data: products = loads(data) else: products = ['Ice Cream', 'Chocolate', 'Coca Cola', 'Hamburger'] redis.set('products', dumps(products)) return {'products': products} api.add_resource(Product, '/api/products')
html文件夾用來保存靜態頁面,稍後我們會通一個運行Nginx的容器來向瀏覽器提供靜態頁面。index.html文件的內容如下所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>首頁</title> </head> <body> <div id="app"> <h2>產品列表</h2> <ul> <li v-for="product in products">{{ product }}</li> </ul> </div> <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script> <script> new Vue({ el: '#app', data: { products: [] }, created() { fetch('http://1.2.3.4:8000/api/products') .then(resp => resp.json()) .then(json => {this.products = json.products}) } }) </script> </body> </html>
接下來,我們要通過docker-compose.yml文件來創建三個容器並指明容器之間的依賴關係。
version: '3' services: api-server: build: ./myapp ports: - '8000:8000' links: - redis-master web-server: image: nginx ports: - '80:80' volumes: - ./html:/usr/share/nginx/html redis-master: image: redis expose: - '6379'
有了這個YAML文件,我們就可以使用
docker-compose
命令來創建容器運行項目,其命令如下所示:[ECS-root temp]# docker-compose up Creating network "temp_default" with the default driver Creating temp_web-server_1 ... done Creating temp_redis-master_1 ... done Creating temp_api-server_1 ... done Attaching to temp_redis-master_1, temp_web-server_1, temp_api-server_1 redis-master_1 | 1:C 05 Dec 2019 11:57:26.828 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo redis-master_1 | 1:C 05 Dec 2019 11:57:26.828 # Redis version=5.0.6, bits=64, commit=00000000, modified=0, pid=1, just started redis-master_1 | 1:C 05 Dec 2019 11:57:26.828 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf redis-master_1 | 1:M 05 Dec 2019 11:57:26.830 * Running mode=standalone, port=6379. redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # Server initialized redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled. redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 * Ready to accept connections api-server_1 | [2019-12-05 11:57:27 +0000] [1] [INFO] Starting gunicorn 20.0.4 api-server_1 | [2019-12-05 11:57:27 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1) api-server_1 | [2019-12-05 11:57:27 +0000] [1] [INFO] Using worker: sync api-server_1 | [2019-12-05 11:57:27 +0000] [8] [INFO] Booting worker with pid: 8 api-server_1 | [2019-12-05 11:57:27 +0000] [9] [INFO] Booting worker with pid: 9 api-server_1 | [2019-12-05 11:57:27 +0000] [10] [INFO] Booting worker with pid: 10 api-server_1 | [2019-12-05 11:57:27 +0000] [11] [INFO] Booting worker with pid: 11
要停止容器的運行,可以使用下面的命令。
docker-compose down
實際的生產環境中常常需要部署和管理多個協同工作的容器,docker compose解決了多容器創建和管理的問題,但是實際項目中,我們還需要Kubernetes(以下都簡稱爲K8S)來提供一個跨主機集羣的容器調度平臺。K8S可以進行自動化容器的部署、擴展和操作,從而提供以容器爲中心的基礎架構。該項目是谷歌在2014年啓動的項目,建立在谷歌公司十餘年運維經驗的基礎之上,而且谷歌自己的應用也是運行在容器上的。