安裝Docker
首先要安裝Docker。Docker底層使用的是Linux的容器技術。
所以,爲了能夠使用Docker,我們需要一臺安裝了兼容版本的Linux內核和二進制文件的最小化功能宿主機。
筆者這裏使用了CentOS 7操作系統。
Step1. Update Docker Package Database
更新yum的repo:
sudo yum check-update
Step 2: Install the Dependencies
接下來安裝Docker的依賴庫:
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
The yum-utils switch adds the yum-config-manager. Docker uses a device mapper storage driver, and the device-mapper-persistent-data and lvm2 packages are required for it to run correctly.
yum-utils會安裝yum-config-manager,用於我們下一步配置Docker repo。
Docker需要使用設備存儲映射驅動(device mapper storage driver),因此,爲了Docker能夠正確的運行,我們需要安裝device-mapper-persistent-data 和 lvm2 packages。
Step 3: Add the Docker Repository to CentOS
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
這個yum repo會讓我們安裝最新版本的Docker。
Step 4: Install Docker On CentOS Using Yum
sudo yum install docker
Step: 5 Manage Docker Service
雖然我們安裝了Docker,但是Docker並沒有啓動。Docker是作爲一種服務來運行的:
sudo systemctl start docker
sudo systemctl enable docker
OK,我們就完成了Docker的安裝,並啓動了Docker服務。
安裝Docker-compose
接下來我們來安裝Docker-compose。
Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.
Compose是定義和運行多容器Docker應用程序的工具,使用Compose,您可以使用YAML文件來配置應用程序的服務,然後,使用單個命令創建並啓動配置中的所有服務
在Linux系統中,安裝Docker-compose由兩種方式:
方法一:
sudo curl -L "https://github.com/docker/compose/releases/download/1.25.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
To install a different version of Compose, substitute 1.25.1 with the version of Compose you want to use.
爲Docker-compose添加可執行權限:
sudo chmod +x /usr/local/bin/docker-compose
方法二:
利用pip:
#安裝pip
$yum -y install epel-release
$yum -y install python-pip
#確認版本
$pip --version
#更新pip
$pip install --upgrade pip
#安裝docker-compose
$pip install docker-compose
#查看版本
$docker-compose version
注意在安裝Docker-compose的時候,我們需要安裝一下系統的編譯支持:
yum install gcc
yum install gcc-c++
yum install python-devel -y
編寫Docker file
Docker file用於Docker編譯我們的工程。在編寫Docker的時候,要注意的是,Docker file的路徑,全部是基於Docker file所在目錄的。
下圖是docker file的目錄配置:
最外層的Docker file是用來配置我們的Web 工程,基於python:3.6的鏡像:
FROM python:3.6
RUN mkdir /ai_mei_jia
WORKDIR /ai_mei_jia
ADD . /ai_mei_jia
RUN pip3 install -r requirements.txt
EXPOSE 8080
ENV DJANGO_SETTINGS_MODULE=ai_mei_jia.settings.pro
# 賦予start.sh可執行權限
RUN chmod u+x start.sh
#容器啓動後要執行的命令
CMD bash ./start.sh
requirements.txt中配置需要Django工程依賴的python庫:
uwsgi==2.0.18
psycopg2-binary==2.8.4
Django==3.0.1
djangorestframework==3.11.0
pillow==6.2.1
django-rest-auth==0.9.5
djangorestframework-jwt==1.11.0
django-redis==4.11.0
aliyun_python_sdk_core
start.sh用來在容器啓動時,執行必要的shell命令:
python manage.py collectstatic --noinput &&
python manage.py makemigrations &&
python manage.py migrate &&
uwsgi --ini config/uwsgi.ini
在Nginx目錄中,我們配置了Nginx需要的Docker file,主要是將我們的Nginx配置文件,拷貝到Nginx容器中:
FROM nginx
# 對外暴露端口
EXPOSE 80 8000
COPY ./config/ai_mei_jia_nginx.conf /etc/nginx/conf.d/
在Redis目錄中,配置了Redis需要的Docker file:
FROM redis:5.0
COPY redis.conf /usr/local/etc/redis/redis.conf
CMD [ "redis-server", "/usr/local/etc/redis/redis.conf" ]
編寫Docker-compose.yml
我們編寫好Docker file之後,就可以在Docker-compose.yml文件中,將這些鏡像組織起來了:
version: '3'
services:
db:
image: postgres:12
restart: always
volumes:
- ./postgredata:/var/lib/postgresql/data
environment:
- POSTGRES_USER=eshi
- POSTGRES_DB=ai_mei_jia_db
- POSTGRES_PASSWORD=sw123!
redis:
image: redis:5
restart: always
command: redis-server
ports:
- "6379:6379"
volumes:
- ./redisdata:/data
web:
build: .
restart: always
ports:
- "8080:8080"
volumes:
- .:/ai_mei_jia
- /tmp/:/tmp/
depends_on:
- db
- redis
nginx:
build: ./nginx
ports:
- "80:80"
volumes:
- ./nginx/config:/etc/nginx/conf.d
- /tmp/:/tmp/
- ./media:/usr/share/nginx/html/media
- ./static:/usr/share/nginx/html/static
restart: always
depends_on:
- web
對於鏡像的構建,docker-compose有兩種方式:build
和image
。build的方式後面跟的內容是構建鏡像所需的Docker file的路徑,如:
...
nginx:
build: ./nginx
...
而image的方式則是之間使用倉庫中已有的鏡像,後面跟的內容是鏡像的倉庫路徑,這種情況是不需要本地構建鏡像的,而是直接將倉庫中的鏡像pull到本地。如:
...
redis:
image: redis:5
...
我們用depends_on
設定了容器之間的依賴關係,在容器啓動時,會根據depends_on
的順序來依次啓動容器。需要注意的是,這裏的容器啓動,並不能夠確保容器內的數據庫能夠完全啓動,因此可能會發生web連接不上db的問題。
我們用volumes
設定了宿主機與容器之間的卷映射
,這樣做的好處是,我們能夠直接在宿主機修改文件內容,如服務器的源代碼,或一些配置文件,而不必重新構建鏡像。注意,這裏映射的宿主機的路徑,是相對於docker-compose.yml文件的
。
現在,我們在docker-compse文件所在的目錄,執行:
docker-compose build
來構建我們的鏡像吧!
Docker Networking
在docker-compose文件中,我們一共構建了4個鏡像:
db, redis, web, nginx。(當然最後的鏡像名稱並不是這4個名稱,這4個名稱主要是用來在docker-compose中容器間的通信)。
我們用depend-on設置了容器之間的依賴關係。
那麼,這些容器間是如何通信的呢?
在Docker中,容器之間的通信通過網絡創建,這被稱爲Docker Networking,也是Docker 1.9發佈版本中的新功能。
當我們用docker-compose up
啓動一組容器後,如果沒有指定名稱,則docker會默認創建一個Docker Networking網絡
,容器間通過這個網絡來通信。可以理解爲Docker創建了一個虛擬的局域網,docker-compose中的每個容器,都是這個局域網內的一個主機,由自己的ip,容器通過這個虛擬局域網來通信。
我們可以用下面命令來查看當前Docker中的networking 列表:
docker network ls
NETWORK ID NAME DRIVER SCOPE
63ba93338b3a ai_mei_jia_copy_default bridge local
60090ef6f4ce ai_mei_jia_default bridge local
a5395928d7f8 bridge bridge local
abf7689564f0 host host local
bb9594ed8810 none null local
network分爲兩種,一種是bringe,一種是overlay。通過overlay模式,我們可以使不同的宿主機間的容器進行通信。在這裏,我們只是在同一個宿主機上設置了一個bridge網絡。
我們可以通過inspect
命令來查看一個網絡中的情況:
docker network inspect ai_mei_jia_copy_default
[
{
"Name": "ai_mei_jia_copy_default",
"Id": "63ba93338b3a86d23723209077b2afac1b4afa94c673e63113153f8031b30e71",
"Created": "2020-01-21T14:19:06.957571863+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.19.0.0/16",
"Gateway": "172.19.0.1"
}
]
},
"Internal": false,
"Attachable": true,
"Containers": {
"5354921a5c3caa027f88292913f8924a4d627e9a282a115444a73e468d09f357": {
"Name": "ai_mei_jia_copy_nginx_1",
"EndpointID": "0a4f9714d4f7a3da0847dc2d50fad39b26e8a211b9b9c01091cfdfc2ae20bad5",
"MacAddress": "02:42:ac:13:00:05",
"IPv4Address": "172.19.0.5/16",
"IPv6Address": ""
},
"6ac4a5db1508ac65f9dbe0fea5c698726703a9f1ba23b83a04d9495c298ba75f": {
"Name": "ai_mei_jia_copy_redis_1",
"EndpointID": "df03597ad99434dd735c2bf78cf01777a16ae6c6feaf742c67ca6624ee0ae870",
"MacAddress": "02:42:ac:13:00:02",
"IPv4Address": "172.19.0.2/16",
"IPv6Address": ""
},
"cc56e63dbb76d468a1338b1c621807604fc75ac4e3797067bf544500c2b4e41f": {
"Name": "ai_mei_jia_copy_db_1",
"EndpointID": "67e6f297948f62783737807b577ede01f52fa61b603508f8190b9fd21bd70baf",
"MacAddress": "02:42:ac:13:00:03",
"IPv4Address": "172.19.0.3/16",
"IPv6Address": ""
},
"e852a487cf3ab64fd373ec1f6158fe1f58ac02bfe1201c1ca78afa1f4f8fa50d": {
"Name": "ai_mei_jia_copy_web_1",
"EndpointID": "8e4b4ae0034e207b46bf6b248ab6219bd2622ce5cdf4e6ab94aa345972ab1308",
"MacAddress": "02:42:ac:13:00:04",
"IPv4Address": "172.19.0.4/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {
"com.docker.compose.network": "default",
"com.docker.compose.project": "ai_mei_jia_copy",
"com.docker.compose.version": "1.25.1"
}
}
]
當容器通過docker-compse連接起來後,容器之前的地址就可以用docker-compose配置文件中的名稱來代替了,如,在Django的配置文件中,我們可以設置redis和Postgresql的地址爲:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
...,
'HOST': 'db', # set in docker-compose.yml
'PORT': 5432 # default postgres port
}
}
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://redis:6379/1', # redis(容器)
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
將鏡像上傳至阿里雲鏡像倉庫
當本地鏡像構建完畢後,我們就需要將鏡像上傳當倉庫,用來在其他的機器上使用我們的鏡像。Docker由官方的鏡像網站Docker Hub。由於其是國外的網站,上傳下載都比較慢,因此我們選用了國內的阿里雲Docker鏡像網站。
在阿里雲的控制檯搜索容器鏡像服務:
開通服務後,創建一個鏡像倉庫:
當鏡像創建完畢後,你就會得到一個鏡像倉庫的地址,copy下。
回到宿主機,登錄我們剛纔的阿里雲容器倉庫:
sudo docker login --username=erenshi registry.cn-hangzhou.aliyuncs.com
爲我們本地的鏡像打tag:
docker tag ai_mei_jia_web:v3 registry.cn-hangzhou.aliyuncs.com/ai_mei_jia_test/ai_mei_jia_test1/ai_mei_jia_web:v3
push 到倉庫:
docker push registry.cn-hangzhou.aliyuncs.com/ai_mei_jia_test/ai_mei_jia_test1/ai_mei_jia_web:v3
等待一會兒,阿里雲的容器倉庫裏就有我們的鏡像啦。不過很奇怪的是,在阿里雲的控制檯中,並看不到我們上傳的鏡像,這也許是阿里雲的一個bug:
現在,就來修改docker-compose文件中的內容,將本地的build指令,改爲使用倉庫中的鏡像:
將
web:
build: .
修改爲:
web:
image: registry.cn-hangzhou.aliyuncs.com/ai_mei_jia_test/ai_mei_jia_test1/ai_mei_jia_web:v3
將
nginx:
build: ./nginx
修改爲:
nginx:
image: registry.cn-hangzhou.aliyuncs.com/ai_mei_jia_test/ai_mei_jia_test1/ai_mei_jia_copy_nginx:v1
OK! 讓我們到新的宿主機上,將docker-compose.yml拷貝過來,並將我們的web源碼拷貝到對應的目錄(因爲我們在docker-compose中設置了卷映射,源碼會完全映射到web容器的work 目錄,所以如果不將源碼複製過來,web容器會報找不到文件錯誤)。
編配鏡像:
docker-compose build
編配鏡像,因爲我們此時的鏡像都採用了image模式編配,因此build並不會執行任何事情,而是會等到真正運行的時候,docker纔會到倉庫中將需要的鏡像pull下來。
運行鏡像:
docker-compose up -d
這時docker會到倉庫將鏡像pull到本地,並運行容器。
輸入
docker ps
查看容器的運行狀態,如果正常的話,應該都是up
狀態:
如果發現有的容器一直是Restaring狀態,有可能是容器內部在啓動時發生了錯誤。這時候可以執行:
docker logs CONTAINER_ID
來查看容器輸出的log。
如果還有其他的問題,我們還可以通過命令:
docker exec -it CONTAINER_ID /bin/bash
登錄到容器內部。
可能的問題
在docker環境中,可能的問題多是由於Linux防火牆或SELinux引起的。
對於防火牆,我們可以通過命令
firewall-cmd -state
來查看防火牆的運行狀態,
並通過:
sudo firewall-cmd --zone=public --permanent --add-port=80/tcp
sudo firewall-cmd --reload
來允許http協議通過80端口來訪問我們的服務器。
對於SELinux,我們可以通過命令
sestatus
來查看SELinux的運行狀態,並通過修改SELinux的配置文件:
vim /etc/selinux/config
來設置是否啓動SELinux。設置完畢後,需要通過命令
shutdown -r now
重啓機器來使設置生效。
容器無法解析域名
當我們在容器內部用域名訪問外網時,有時可能出現name unresolved
的問題。如筆者在使用阿里雲SDK發送短信時,SDK提示name unresolved
錯誤。這是因爲容器的DNS沒有設置正確引起的,這多發生在用虛擬機運行Linux的情況下。
這時候,我們需要修改docker的deamon.json文件:
vim /etc/docker/daemon.json
添加宿主機的DNS地址:
{
"dns": ["192.168.57.5"],
}
然後重啓docker服務:
systemctl restart docker
無法訪問服務器
但我們在阿里雲服務器正確啓動服務器容器後,通過瀏覽器訪問服務器網址,卻發現無法訪問服務器。這是爲啥呢?
原來是因爲阿里雲服務器的安全組默認沒有開放80端口的緣故。這時候我們需要到阿里雲ECS控制檯,選擇我們的服務器實例,進入後,選擇本實例安全
組:
在內網入方向全部規則
下面,添加80端口(即Nginx所監聽的端口號):
再次嘗試訪問網址,這次服務器應該就通了!