公式
リファレンス
DockerHub
ディレクトリ構造
下記のようなディレクトリ構造で構築していきます。
コードについてはGitHubにて公開しています。
├── Makefile
├── README.md
├── docker-compose.yml
├── proxy
│ ├── Dockerfile
│ ├── conf.d
│ │ └── default.conf
│ └── nginx.conf
├── webapp_flask
│ ├── Dockerfile
│ ├── main.py
│ └── requirements.txt
├── webapp_go
│ ├── Dockerfile
│ ├── Makefile
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ └── static
│ ├── handler.go
│ ├── main.go
│ ├── server.go
│ └── template.go
└── webapp_sh
├── Dockerfile
├── index.html
└── main.sh
APIサーバ定義
FlaskとGo言語で簡単なAPIサーバを定義しておきます。
Flask (Python)
Dockerで動作させるための最低限のファイルを用意します。
Flaskではエンドポイントとして「/api/flask/hello」を実装します。
(Flaskとは、pythonのWebフレームワークで軽量なWebフレームワークであり、小さく使い始めることができるのが特徴です)
# !/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask
from flask import abort, jsonify, request
import json
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello world'
@app.route('/api/flask/hello')
def api():
return jsonify({'ip': request.environ.get('HTTP_X_REAL_IP', request.remote_addr)}), 200
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=80)
FROM python:3.7.4-alpine3.9
WORKDIR /home/app
COPY . .
RUN pip install -U pip && pip install -r requirements.txt
ENTRYPOINT ["python", "main.py"]
CMD [""]
Flask==1.1.1
Ecoh (Golang)
golangとechoを使って簡単にAPIを実装します。
Echoではエンドポイントとして「/api/go/hello」を実装します。
EchoはgolangのWeb FWでHTMLも出力できます。
ただネット上で出ている情報数の多さ的にはAPIサーバとしてのユースケースが多いように感じました。
(詳しく調査したわけではないです。)
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
initRouting(e)
e.Logger.Fatal(e.Start(":80"))
}
func initRouting(e *echo.Echo) {
e.GET("/", hello)
e.GET("/api/go/hello", test)
}
func test(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"go": "hello"})
}
func hello(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"hello": "world"})
}
## Build ##
FROM golang:alpine3.10 AS build
WORKDIR /home/app
COPY . .
ENV GO111MODULE=on
RUN apk --no-cache add git make build-base \
&& make \
&& go get github.com/labstack/echo/v4
## App ##
FROM alpine:3.9.4
WORKDIR /home/app
RUN apk --no-cache add tzdata && \
cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
COPY --from=build /home/app/main /home/app
EXPOSE 80
ENTRYPOINT ["./main"]
CMD []
Nginx
serverコンテキスト内の記述は下記のように設定します。
今回はパス名ベースでのリバースプロキシなのでproxy_passへプロキシ先のサーバを記述します。
詳細設定については過去に下記のような記事を書いたのでご参照ください。
(今更)Nginx覚書
proxy_set_headerではプロキシ先のWebサーバで送信元IPアドレスを取得する為に設定してます。
server {
listen 80;
server_name www.example.com;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location /api/flask/ {
proxy_pass http://web_flask_upst;
}
location /api/go/ {
proxy_pass http://web_go;
}
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 497 /497.html;
location = /497.html {
root /var/www/html;
internal;
}
}
docker-compose.yml
docker-composeを用いて上記のコンテナを一括で起動していきます。
今回は検証の為全てのコンテナは同一のネットワークへ属するようにします。
各コンテナには「container_name」を設定してコンテナ名で通信が可能になるように設定します。
名前 | 用途 |
---|---|
check | クライアント用のコンテナ。pingやcurlなど基本的なネットワークコマンドを実行 |
proxy | nginx用のコンテナ |
web_flask | flask用のコンテナ |
web_go | go用のコンテナ(Echo) |
---
version: '3.7'
services:
check:
container_name: check
hostname: check_server
image: centos:centos7
tty: true
proxy:
container_name: proxy
hostname: reverse_proxy
build: ./proxy
deploy:
replicas: 1
ports:
- "80:80"
- "443:443"
volumes:
- ./proxy/nginx.conf:/etc/nginx/nginx.conf
- ./proxy/conf.d/default.conf:/etc/nginx/conf.d/default.conf
web_flask:
container_name: web_flask
hostname: web_flask
build: ./webapp_flask
deploy:
replicas: 1
web_go:
container_name: web_go
hostname: web_flask
build: ./webapp_go
deploy:
replicas: 1
networks:
default:
ipam:
config:
- subnet: 192.168.2.0/24
SSL化(オレオレ証明書)
リバースプロキシでのSSLオフロードを試す為だけに自己署名証明書を作成します。
$ docker-compose exec proxy bash
$ cd /etc/nginx/ssl/
# 秘密鍵の生成
openssl genrsa 2048 > server.key
# 証明書署名要求の作成
$ openssl req -new -key server.key > server.csr
# 自己署名
$ openssl x509 -days 3650 -req -signkey server.key < server.csr > server.crt
# 確認
$ ls -l
server.crt server.csr server.key
# SSLサーバ証明書の内容
## 公開鍵の情報
## コモンネーム
## 公開鍵の所有者情報
## 所有者を証明した認証局の情報
## 証明書の有効期限
## 証明書のシリアル番号
## 証明書の失効リスト参照先
上記のパスで証明書を生成したらあとはnginx.confへsslの設定を追加するだけで設定が完了します。
server {
listen 443 ssl;
server_name localhost;
ssl_certificate_key /etc/nginx/ssl/server.key;
ssl_certificate /etc/nginx/ssl/server.crt;
}
若干余談ですが切れがちな証明書の有効期限をチェックするスクリプトを書いてる人がいたので便利!と思ったので紹介します。
opensslコマンドで期限を確認し現在時刻と比較する処理をループしてるようです。
この結果をなんらかの通知先(slackなど)へやっておくとアクセスした時に初めて気づくなどのようなことが減りそうで良さそうですね。
(余談)リバースプロキシメモ(proxy_next_upstream)
proxy_next_upstreamはリクエストの送信先)からエラーが返されたりした際の動作に関する設定を行います。
proxyしたサーバーからのレスポンスがstatus_code:5xxだった場合やタイムアウトなどの時にリトライする仕組みを設定できます。
upstream flask_app {
server 192.168.1.1;
server 192.168.1.2;
server 192.168.1.3;
}
location / {
proxy_pass http://flask_app;
proxy_next_upstream http_500;
}
}
上記の設定ではupstremで設定したサーバへproxyした結果が500を返したきた場合は次のサーバへリクエストをリトライするような設定です。
ちなみにerror_pageを無効にしないとupstreamの設定と競合するので注意が必要です。
設定 | 概要 |
---|---|
http_500 | a server returned a response with the code 500 |
http_502 | a server returned a response with the code 502; |
http_503 | a server returned a response with the code 503; |
http_504 | a server returned a response with the code 504; |
http_403 | a server returned a response with the code 403; |
http_404 | a server returned a response with the code 404; |
http_429 | a server returned a response with the code 429; |