Photo by Jan Vanveen / Unsplash

Background

本章延續上一章作更進一步的實作. 我們想要利用 Docker Compose 來完成多個 services 的container 實作. 那我自己心中比較理想的是一個 container 就封裝一個 application. 所以整個的架構會比較像下圖一樣. 一個封裝 Nginx, 另一個則為 Flask Application.

Flask-Docker

Start

接著我們進一步來討論 Docker Compose, Docker Compose 是一個工具可以讓你可以透過指令來控制專案中所需要的 services. 那在執行Docker Compose相關指令前, 我們必須撰寫 docker-compose.yml 來告訴 Docker Compose 怎麼使用這些 services. 詳細的部份我們稍後再用例子來說明

Directory

整個專案的目錄架構如下所示, 在主目錄中我們分別有兩個資料夾和一個檔案. 基本上如果我有幾個service需要呼叫的話我就會開幾個資料夾. 那目前我們只有兩個service. (Flask & Nginx)

  • hello: flask application
  • nginx: nginx application
  • docker-compose.yml
.
├── docker-compose.yml
├── hello
│   ├── Dockerfile
│   ├── __pycache__
│   │   └── run.cpython-36.pyc
│   ├── requirements.txt
│   ├── run.py
│   └── src
│       ├── __init__.py
│       ├── main.py
│       └── __pycache__
│           ├── __init__.cpython-36.pyc
│           └── main.cpython-36.pyc
└── nginx
    ├── conf.d
    │   └── hello_flask.conf
    ├── Dockerfile
    └── nginx.conf

接著我們一個一個資料夾來看

hello

hello 專案的程式碼其實跟上一篇 自行建立 Docker 影像檔 很像. 差別在於這邊是用 gunicorn 來啟動 Flask service.

$ mkdir hello
$ cd hello
$ virtualenv .venv
$ source .venv/bin/activate
$ mkdir src
$ touch Dockerfile run.py src/main.py src/__init__.py
$ pip3 install flask gunicorn
$ pip3 freeze > requirements.txt

run.py

from src import app

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

src/main.py

from src import app

@app.route('/')
def hello_world():
    return 'Hello, World!'

src/__init__.py

from flask import Flask

app = Flask(__name__)

from src import main

Dockerfile

FROM ubuntu:16.04

RUN apt-get update
RUN apt-get install -y software-properties-common vim
RUN add-apt-repository ppa:jonathonf/python-3.6
RUN apt-get update

RUN apt-get install -y build-essential python3.6 python3.6-dev python3-pip python3.6-venv
RUN apt-get install -y git

# update pip
RUN python3.6 -m pip install pip --upgrade
RUN python3.6 -m pip install wheel

RUN mkdir /app

WORKDIR /app
# 將本機 app 拷貝到 image /app 中
ADD . /app

RUN pip3 install virtualenv
RUN virtualenv venv
RUN /app/venv/bin/pip3 install -r requirements.txt

# 讓 5000 連接埠可以從 Docker 容器外部存取
EXPOSE 5000

# 當 Docker 容器啟動時,自動執行 app.py
CMD ["/app/venv/bin/gunicorn", "--workers=3", "run:app", "-b", "0.0.0.0:5000"]

上述檔案都弄好只好, 接著下來我們要先建立 Flask service 的 image 檔案. 這樣這部分就到一個段落.

$ docker build -f Dockerfile -t hello .

nginx

接著我們來處理 nginx 的部分.

$ mkdir nginx
$ cd nginx
$ mkdir conf.d
$ touch Dockerfile nginx.conf conf.d/hello_flask.conf

nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

conf.d/hello_flask.conf

server {
    listen 80;

    location / {
        proxy_pass http://web:5000;
        proxy_redirect   off;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

}

這邊值得注意的部分是 proxy_pass http://web:5000; 這行指令. 這行指令跟 docker-compose.yml 的內有有關. 我們等等會解釋.

Dockerfile

FROM nginx

RUN rm /etc/nginx/nginx.conf
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/
COPY conf.d /etc/nginx
COPY conf.d/hello_flask.conf /etc/nginx/conf.d

VOLUME /etc/nginx/
VOLUME /etc/nginx/conf.d

這邊一樣我們要建一個image fo docker-compose.

$ docker build -f Dockerfile -t nginx-flask .

docker-compose.yml

version: "3.3"

services:
  web:
    image: hello
    expose:
      - "5000"
    ports:
      - "5000:5000"

  nginx:
    image: nginx-flask
    ports:
      - "80:80"
    depends_on:
      - web

這邊稍微簡單解釋一下:

  • version: 目前使用的版本, 可以參考官網
  • services: 關鍵字後面列出 web, nginx 兩項專案中的sevices
  • image: 利用指定的image build 出 container
  • port: 外部露出開放的 port 對應到 docker container 的 port
  • expose: container 對外開放的 port
  • depends_on: 這個service跟哪一個service有關.

首先要先了解的兩個container之間的network互通不是透過localhost. 所以nginx那邊才要改成web (proxy_pass http://web:5000;, 常見的為 proxy_pass http://localhost:5000;). 必須明確告知是要把 request 導到哪個 container 中.

完成 docker-compose.yml 後我們就可以利用 docker-compose 指令來喚起整個服務. (沒安裝指令的話請自行上官網查安裝文件.)

$ docker-compose up

打開瀏覽器輸入 server 的 ip 應該就可以順利看到 Hello, World! 的字眼了.

Reference