Dockerを使ってstreamlitをhttps化する

## 概要

- dockerを用いてstreamlitをhttps化する方法を記載します

## TL;DR

- 本記事の簡単な流れ
  1. dockerでsreamlitを起動
  2. dockerでstreamlitとnginxを起動
  3. 証明書を発行
  4. https化しdockerを起動する

## 前提条件

- docker-composeをinstallしていること

## 目次

0. 最終的なディレクトリ構成
1. dockerでstreamlitを起動する
   1. ファイルを用意する
   2. streamlitで呼び出すためのpythonファイルを作成する
   3. ``Dockerfile``を作成する
   4. ``docker-compose.yml``を作成する
   5. 起動する
2. nginxを使ってstreamlitを起動する
   1. ``nginx.conf``を作成する
   2. ``Dockerfile``を作成する
   3. ``docker-compose.yml``を修正する
   4. 修正内容を反映して起動する
3. 証明書を用意する
   1. mkcertをインストールする
   2. 証明書を発行する
4. https化する
   1. ``nginx.conf``を修正する
   2. ``Dockerfile``を修正する
   3. ``docker-compose.yml``を修正する
   4. 起動する

---

### 0. 最終的なディレクトリ構造

- 最終的なディレクトリ構造
  ```
  https_streamlit
    |- streamlit
    |   |- app.py
    |   |- Dockerfile
    |
    |- nginx
    |    |-keys
    |    |  |- localhost+1-key.pem
    |    |  |- localhost+1.pem
    |    |
    |    |- Dockerfile
    |    |- nginx.conf
    |
    |- docker-compose.yml
  ```

---

### 1. dockerでstreamlitを起動する

1. ファイルを用意する

- ディレクトリを下記の状態にする
  ```
  https_streamlit
    |- streamlit
    |   |- app.py
    |   |- Dockerfile
    |- docker-compose.yml
  ```

２. streamlitで呼び出すためのpythonファイルを作成する

``https_streamlit/streamlit/app``の内容
```python
import streamlit as st

st.markdown("# Hello world")
```

- streamlitをinstallしてない人はinstallしましょう
``pip install streamlit``

３. ``Dockerfile``を作成する

- ``https_streamlit/streamlit/Dockerfile``の内容
   ```Dockerfile
   FROM python:3.9.6-buster

   RUN pip install streamlit
   ```

４. ``docker-compose.yml``を作成する

- ``https_streamlit/docker-compose``の内容
  ```yaml
  version: "3.8"
  services:
    streamlit:
      build: ./streamlit
      volumes:
        - ./streamlit:/streamlit
      ports:
        - 8502:8501
      command: streamlit run ./streamlit/app.py
  ```
- docker-composeファイル ⇦後で追記
  - このcomposeファイルはstreamlitというserviceを定義しています

５. 起動する

- 下記コマンドを実行
 ``docker-compose up -d --build``
- ブラウザ上で確認
  - ``http://localhost:8502(composeファイルで指定したポート)``でアクセスできればOK
　　　　![streamlit_docker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2841151/fe8eac43-9010-86ab-e250-ca15a7550a0a.png)


### 2. nginxを使ってstreamlitを起動する

1. ``nginx.conf``を作成する

- ``https_streamlit/nginx/nginx.conf``の内容
  ```
    events {
        worker_connections  16;
    }
    http{
      server {
        listen 80;
        server_name localhost;
        location / {
            proxy_pass http://streamlit:8501/;
        }
        location ^~ /static {
            proxy_pass http://streamlit:8501/static/;
        }
        location ^~ /healthz {
            proxy_pass http://streamlit:8501/healthz;
        }
        location ^~ /vendor {
            proxy_pass http://streamlit:8501/vendor;
        }
        location /stream {
            proxy_pass http://streamlit:8501/stream;
            proxy_http_version 1.1;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_read_timeout 86400;
        }
      }
    }
  ```

２. ``Dockerfile``を作成する

- ``https_streamlit/nginx/Dockerfile``の内容

  ```Dockerfile
  FROM nginx
  COPY ./nginx.conf /etc/nginx/nginx.conf
  EXPOSE 80
  ```

３. ``docker-compose.yml``を修正する

- ``https_streamlit/docker-compose``の修正内容
  - 修正内容は以下の2点
    - ``services``にnginxを追加
    - networksを追加しコンテナ間での通信を可能にする

  ```yml
  version: "3.8"
  services:
    streamlit:
      build: ./streamlit
      networks:
        shared-nw:
      volumes:
        - ./streamlit:/streamlit
      ports:
        - 8501:8501
      command: streamlit run ./streamlit/app.py
    nginx:
      build: ./nginx
      networks:
        shared-nw:
      ports:
        - 80:80
  networks:
    shared-nw:
      driver: bridge
  ```

４. 修正内容を反映して起動する

- コンテナが起動しているかどうか確認したい場合は下記コマンドを実行する
  - ``docker ps``
    - ``CONTAINER ID``や``IMAGE``の下にimageが存在すれば起動している
- コンテナが起動している場合は下記コマンドを実行しコンテナを停止する
  - ``docker-compose down``
- コンテナが起動していないことを確認し、再度docker-composeファイルを起動する
  - ``docker-compose up -d --build``
- ブラウザ上で起動しているか確認する
   - ``http://localhost``でアクセスできればOK
![nginx_http.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2841151/74fbaf7f-dcd3-ca67-d27d-0a697cb8eed6.png)


### 3. 証明書を用意する

 1. mkcertをインストールする
    - mkcertのインストール
      ```console
      brew install mkcert
      ```
 2. 証明書を発行する
   - ``https_streamlit/nginx/keys``配下で下記コマンドを実行
     - ``mkcert localhost 127.0.0.1``
       - localhostと入れることでlocalhostの証明書を発行できます。
   - ``localhost+1-key.pem``と``localhost+1.pem``が生成されます。

### 4. https化する

1. ``nginx.conf``を修正する

- ``https_streamlit/nginx/nginx.conf``の内容
  - 主にserver配下を修正
    - ``listen``を80から443へ修正
    - ``ssl_certificate``と``ssl_certificate_key``を追加

  ```
  events {
      worker_connections  16;
  }
  http{
    server {
      listen 443 ssl;
      server_name  localhost;
      ssl_certificate /etc/keys/localhost.pem;
      ssl_certificate_key /etc/keys/localhost-key.pem;
      location / {
          proxy_pass http://streamlit:8501/;
      }
      location ^~ /static {
          proxy_pass http://streamlit:8501/static/;
      }
      location ^~ /healthz {
          proxy_pass http://streamlit:8501/healthz;
      }
      location ^~ /vendor {
          proxy_pass http://streamlit:8501/vendor;
      }
      location /stream {
          proxy_pass http://streamlit:8501/stream;
          proxy_http_version 1.1;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header Host $host;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";
          proxy_read_timeout 86400;
      }
    }
  }
  ```

２. ``Dockerfile``を作成する

- ``https_streamlit/nginx/Dockerfile``の修正内容
  - ADDでssl証明書のkeyをdockerコンテナ内でも使用可能にするために追記

  ```Dockerfile
  FROM nginx

  COPY ./nginx.conf /etc/nginx/nginx.conf
  ADD ./keys/localhost+1.pem /etc/keys/localhost.pem
  ADD ./keys/localhost+1-key.pem /etc/keys/localhost-key.pem

  EXPOSE 443
  ```

３. ``docker-compose.yml``を修正する

- ``https_streamlit/docker-compose``の修正内容
  - 修正内容
    - ``service.nginx``配下のportを443へ修正

   ```yml
   version: "3.8"
   services:
     streamlit:
       build: ./streamlit
       networks:
         shared-nw:
       volumes:
         - ./streamlit:/streamlit
       ports:
         - 8502:8501
       command: streamlit run ./streamlit/app.py
     nginx:
       build: ./nginx
       networks:
         shared-nw:
       ports:
         - 443:443
   networks:
     shared-nw:
       driver: bridge
   ```

４. 起動する

- コンテナが起動しているかどうか確認したい場合は下記コマンドを実行する
  - ``docker ps``
    - ``CONTAINER ID``や``IMAGE``の下にimageが存在すれば起動している
- コンテナが起動している場合は下記コマンドを実行しコンテナを停止する
  - ``docker-compose down``
- コンテナが起動していないことを確認し、再度docker-composeファイルを起動する
  - ``docker-compose up -d --build``
- ブラウザ上で起動しているか確認する
   - ``https://localhost``でアクセスできればOK
![https_nginx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2841151/40874cbc-c970-4aed-81f8-02969724702c.png)


## 最後に

- ``nginx.conf``の書き方には苦労しました。
  - まだわかっていないところが多々あります。
  - これからも学習を続けていきます。
- この方法を使えばstreamlitだけでなくFlaskなどもhttps化できます。

---

### 参考

- https://docs.docker.jp/compose/gettingstarted.html#compose-gettingstarted-prerequisites
- https://www.codebu.org/docker/docker-https-nginx/
- https://github.com/FiloSottile/mkcert