Docker 環境で C++ Pistache Webサービスを起動する

# Docker 環境で C++ Pistache Webサービスを起動する

こんにちは、@studio_meowtoon です。今回は、WSL Ubuntu の Docker 環境で、C++ Pistache Web アプリケーションをコンテナとして起動する方法を紹介します。
![pistache_on_docker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/44eae88f-3350-7876-7614-7a7badcbae53.png)

## 目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

https://qiita.com/studio_meowtoon/items/6119ab05d8b13527efd8

## 実現すること

ローカル環境の WSL Ubuntu の Docker 環境で、Dockerfile からビルドした C++ Pistache Web サービスのカスタムコンテナイメージを起動します。

**ネイティブイメージ形式のアプリをコンテナとして起動**
```mermaid
flowchart LR
terminal --> app[app]
subgraph Ubuntu
  subgraph Docker
    subgraph container[app-hello-pistache]
      subgraph app[app]
        pistache
      end
    end
  end
end
style Ubuntu fill:#FFBCA4, stroke:#E0460F
style Docker fill:#EEFDFF, stroke:#74B9DC
style container fill:#FFD7E4, stroke:#E13C73
style app fill:#ECECFF, stroke:#9674DC
style pistache fill:#FFFFDE, stroke:#B4AE43
```

**実行環境**

|要素|概要|
|:--|:--|
|terminal|ターミナル|
|Ubuntu|OS|
|Docker|コンテナ実行環境|

**Web サービス コンテナ**

|要素|概要|
|:--|:--|
|app-hello-pistache|カスタムコンテナ|
|app|ネイティブイメージ アプリケーション|
|pistache|Web サーバー|

## 技術トピック

### Pistache とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### [Pistache (ピスターシュ)](https://pistacheio.github.io/pistache/docs/)

:::note info
Pistacheは、C++ で書かれた軽量な Web サーバーフレームワークです。

|キーワード|内容|
|:--|:--|
|軽量|Pistache はコンパクトな設計であり、軽量なアプリケーションやマイクロサービスに適しています。|
|モダンな C++|C++11 以降のモダンな C++ スタンダードを活用しています。|
|RESTful APIサポート|Pistache は RESTful API を構築するために設計されており、HTTP メソッド (GET、POST など) の処理を容易に行えます。|
|マルチスレッド|マルチスレッドをサポートし、並行リクエストの処理が可能です。|
|イベント駆動|Pistache は非同期およびイベント駆動のアーキテクチャを採用しており、高いパフォーマンスを実現します。|
|シンプルな API 設計|Pistache のシンプルな API 設計により、Web サーバーの構築や API のエンドポイントの設定が簡単です。|
|パフォーマンス|軽量かつ高性能な設計により、効率的なリクエスト処理が可能です。|
|拡張性|ユーザー定義のミドルウェアやカスタムコンポーネントを統合することが容易です。|
|プラットフォーム非依存|Pistache はプラットフォーム非依存であり、さまざまな環境で利用できます。|
|アクティブなコミュニティ|Pistache はアクティブなオープンソースコミュニティによってサポートされており、ドキュメントやサンプルコードが充実しています。|
:::

</details>

### Dockerfile とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### [Dockerfile](https://docs.docker.jp/engine/reference/builder.html#dockerfile)

:::note info
Dockerfile は、Docker コンテナを構築するためのテキストファイルです。Docker コンテナはアプリケーションやサービスを実行するための環境を含む軽量でポータブルな仮想化ユニットです。

|キーワード|内容|
|:--|:--|
|スクリプト形式|Dockerfile はシンプルなスクリプト形式で記述されるため、コンテナのビルドプロセスを自動化することが容易です。|
|レイヤー構造|Docker イメージは Dockerfile の各命令が実行される際にレイヤーとして生成され、再利用やキャッシュが可能な構造となっています。|
|バージョン管理|Dockerfile はテキストベースであるため、コードと同様にバージョン管理システムで管理しやすいです。|
|ポータビリティ|Dockerfile により、アプリケーションとその依存関係が1つのコンテナイメージにパッケージ化されるため、異なる環境間での移植性が高まります。|
|自動化と効率化|Dockerfile を使用することで、アプリケーションのビルドや環境構築を自動化できます。これにより、手動での作業時間やヒューマンエラーが減り、開発・デプロイプロセスが効率的になります。|
|再現性|Dockerfile はビルド手順を完全に定義するため、異なる環境で同じアプリケーションを再現できます。これにより、開発、テスト、本番環境間での一貫性が確保されます。|
|環境の分離|Docker コンテナはホストシステムから分離されるため、アプリケーションの依存関係やライブラリの衝突を回避し、より安全な環境で実行できます。|
|拡張性|Dockerfile を使用することで、カスタムイメージを作成することができます。このため、特定のニーズに合わせてカスタマイズされたコンテナイメージを容易に作成できます。|
:::

</details>

## 開発環境

+ Windows 11 Home 22H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作していきますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
> wsl --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
```

Docker [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe)

```sh
$ docker --version
Docker version 23.0.1, build a5ee5b1
```

</details>

:::note warn
この記事では基本的に **Ubuntu のターミナルで操作**を行います。**Vim を使用してコピペする方法**を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
:::

https://qiita.com/studio_meowtoon/items/7771ad48676926e41463

## 作成する Web アプリケーションの仕様

|No|エンドポイント|HTTPメソッド|MIME タイプ|
|:--|:--|:--|:--|
|1|/api/data|GET|application/json|

:::note info
**/api/data** というエンドポイントに対して HTTP GET リクエストを送信すると、JSON データがレスポンスされるシンプルな Web サービスを実装します。
**{"message":"Hello World!"}**
:::

## Hello World を表示する手順

### g++ (GNU Compiler Collection) のインストール

C++ コードをコンパイルするために、g++ をインストールします。

```sh
$ sudo apt update
$ sudo apt install g++
```

確認します。

```sh
$  g++ --version
g++ (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0
```

### Pistache のインストール

Pistache をインストールします。

```sh
$ sudo add-apt-repository ppa:pistache+team/unstable
$ sudo apt update
$ sudo apt install libpistache-dev
```

### プロジェクトの作成

プロジェクトフォルダを作成します。  
※ ~/tmp/hello-pistache をプロジェクトフォルダとします。

```sh
$ mkdir -p ~/tmp/hello-pistache
$ cd ~/tmp/hello-pistache
```

### アプリケーションファイルの作成

**main.cpp** ファイルを作成します。

```sh
$ vim main.cpp
```

ファイルの内容

```cpp:main.cpp
#include <csignal>
#include <iostream>
#include <pistache/endpoint.h>
#include <pistache/http.h>
#include <pistache/http_headers.h>

using namespace Pistache;
using namespace Pistache::Http;
using namespace Pistache::Http::Header;
using namespace std;

class AppHandler : public Handler {

public:
    HTTP_PROTOTYPE(AppHandler)
    void onRequest(const Request& request, ResponseWriter response) override {
        // check the requested resource.
        if (request.resource() == "/api/data") {
            // set the content-type header.
            response.headers().add<ContentType>(MIME(Application, Json));
            // respond with a json message.
            response.send(Code::Ok, R"({"message":"Hello World!"})");
        } else {
            // respond with a not found error.
            response.send(Code::Not_Found);
        }
    }
};

int main() {
    // set receive port.
    Port port(9080);
    Address addr(Ipv4::any(), port);
    // start the server.
    cout << "Server is starting..." << endl;
    // register the shutdown handler.
    signal(SIGINT, [](int /* signal */) {
        cout << "Shutting down gracefully..." << endl;
        exit(0);
    });
    // listen and serve requests using the custom handler.
    listenAndServe<AppHandler>(addr);
    return 0;
}
```

### アプリケーションのビルド

ソースコードをビルドします。

```sh
$ g++ -std=c++17 \
    -o app main.cpp \
    -lpistache -lpthread
```

### ローカルで実行

ローカルで実行します。
停止するときは ctrl + C を押します。

```sh
$ ./app
```

別ターミナルから curl コマンドで確認します。

```sh
$ curl -v http://localhost:9080/api/data -w '\n'
```

出力

```sh
*   Trying 127.0.0.1:9080...
* Connected to localhost (127.0.0.1) port 9080 (#0)
> GET /api/data HTTP/1.1
> Host: localhost:9080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json
< Connection: Close
< Content-Length: 26
<
* Closing connection 0
{"message":"Hello World!"}
```

:::note info
ここまでの手順で、ターミナルに **{"message":"Hello World!"}** と表示され、**JSON データ**を取得することが出来ました。
:::

### コンテナイメージをビルド

Dockerfile を作成します。

```sh
$ vim Dockerfile
```

ファイルの内容

```Dockerfile:Dockerfile
# build the app.
FROM debian:12-slim as build-env

# set the working dir.
WORKDIR /app

# install build tools and libraries.
RUN apt-get update && \
    apt-get install -y build-essential cmake git libssl-dev g++ && \
    # install libpistache-dev.
    apt-get update && \
    apt-get install -y libpistache-dev

# copy main.cpp to the working dir.
COPY main.cpp /app/main.cpp

# build the app and optimize its size.
RUN g++ -std=c++17 -o app main.cpp -O2 -lpistache -lpthread && \
    strip app 

# set up the container.
FROM debian:12-slim

# set working dir.
WORKDIR /app

# copy the built app binary from the build-env.
COPY --from=build-env /app/app ./app

# install libpistache-dev and other tools.
RUN apt-get update && \
    apt-get install -y libpistache-dev && \
    # clean up package cache and temporary files.
    apt-get clean && rm -rf /var/lib/apt/lists/*

# expose the port.
EXPOSE 9080

# command to run the app.
CMD ["./app"]
```

Docker デーモンを起動します。

```sh
$ sudo service docker start
 * Starting Docker: docker    [ OK ]
```

:::note warn
Docker 環境をお持ちでない場合は、以下の関連記事から Docker Engine のインストール手順をご確認いただけます。
:::

https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe

コンテナイメージをビルドします。

```sh
$ docker build \
    --no-cache \
    --tag app-hello-pistache:latest .
```

コンテナイメージを確認します。

```sh
$ docker images | grep app-hello-pistache
app-hello-pistache   latest    c8f5df43c872   About a minute ago   98.4MB
```

:::note info
ここまでの手順で、ローカル環境の **Docker** にアプリの**カスタムコンテナイメージ**をビルドすることができました。
:::

### コンテナを起動

ローカルでコンテナを起動します。
※ コンテナを停止するときは ctrl + C を押します。

```sh
$ docker run --rm \
    --publish 9080:9080 \
    --name app-local \
    app-hello-pistache:latest
```

:::note info
ここまでの手順で、ローカル環境の **Docker** でアプリの**カスタムコンテナ**を起動することができました。
:::

### コンテナの動作確認

別ターミナルから curl コマンドで確認します。

```sh
$ curl -v http://localhost:9080/api/data -w '\n'
```

出力

```sh
*   Trying 127.0.0.1:9080...
* Connected to localhost (127.0.0.1) port 9080 (#0)
> GET /api/data HTTP/1.1
> Host: localhost:9080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json
< Connection: Close
< Content-Length: 26
<
* Closing connection 0
{"message":"Hello World!"}
```

:::note info
ここまでの手順で、ターミナルに **{"message":"Hello World!"}** と表示され、**JSON データ**を取得することが出来ました。 
::: 

コンテナの状態を確認してみます。

```sh
$ docker ps
CONTAINER ID   IMAGE                       COMMAND   CREATED          STATUS          PORTS                                       NAMES
b85c71da88ab   app-hello-pistache:latest   "./app"   15 seconds ago   Up 14 seconds   0.0.0.0:9080->9080/tcp, :::9080->9080/tcp   app-local
```

### コンテナに接続

別ターミナルからコンテナに接続します。  

```sh
$ docker exec -it app-local /bin/sh
```

コンテナに接続後にディレクトリを確認します。
※ コンテナから出るときは ctrl + D を押します。

```
# pwd
/app
# ls -lah
total 32K
drwxr-xr-x 1 root root 4.0K Aug 10 09:53 .
drwxr-xr-x 1 root root 4.0K Aug 10 09:58 ..
-rwxr-xr-x 1 root root  23K Aug 10 09:53 app
```

top コマンドで状況を確認します。

```
# apt update
# apt install procps
# top
top - 10:00:11 up 10:09,  0 user,  load average: 0.00, 0.05, 0.05
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.1 sy,  0.0 ni, 99.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7897.1 total,    172.0 free,   2680.4 used,   5340.2 buff/cache
MiB Swap:   2048.0 total,   1992.9 free,     55.1 used.   5216.7 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    1 root      20   0   85516   3624   2964 S   0.0   0.0   0:00.02 app
   15 root      20   0    2576    904    816 S   0.0   0.0   0:00.01 sh
  205 root      20   0    8560   4636   2768 R   0.0   0.1   0:00.00 top
```

コンテナの情報を表示してみます。

```csharp
# cat /etc/*-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
```

:::note warn
このコンテナは Debian GNU/Linux をベースに作成されています。つまり、Debian GNU/Linux と同じように扱うことができます。
:::

### ディレクトリ・ファイル構成

プロジェクトのファイル構成を表示してみます。

```sh
$ tree
.
├── Dockerfile
├── app
└── main.cpp
```

## まとめ

WSL Ubuntu の Docker 環境で、Dockerfile からビルドした C++ Pistache Web サービスのカスタムコンテナを起動することができました。

:::note warn
クラウド開発においては、**Dockerfile の理解**は重要です。自動ビルドツールもありますが、手動で書く必要があるケースもあります。**Ubuntu** を使うと **Linux の知識**も身に付きます。最初は難しく感じるかもしれませんが、徐々に進めていけば自信を持って書けるようになります。
:::

どうでしたか？ WSL Ubuntu で、C++ Pistache Web アプリケーションを手軽に実行することができます。ぜひお試しください。今後も C++ の開発環境などを紹介していきますので、ぜひお楽しみにしてください。

## 推奨コンテンツ

https://qiita.com/studio_meowtoon/items/12679377e7182223a0be