小白服務器編程指北(2)——用Docker編配你的服務器環境

安裝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有兩種方式:buildimage。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所監聽的端口號):
在這裏插入圖片描述

再次嘗試訪問網址,這次服務器應該就通了!

發佈了89 篇原創文章 · 獲贊 47 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章