S3が極貧サービスのお金を溶かしはじめたので、10分の1までコストカットした件

個人でほそぼそとWebサービス作ったら、年間でとりあえず2桁万円の売上にまでなったけど、S3のコストに売上を全部持っていかれそうになったので、対策した話です。

そこまでの金額になると、なぜ気づかなかったかというと

- S3が最初の12ヶ月間無料なので、有料に切り替わった時点でどのくらいのコストになるかわかってなかった
    - 仕事で使ってたけど、その時はそこまでコスト面で気したことなかった（会社のお金なので）
- 12ヶ月終わったタイミングで通知来たけど、開発や運用で忙しかったしそこまでの金額にならないと思って気にしてなかった
    - 2ヶ月目で気づいて本当によかった

# 読者対象

- 個人でサービスをやってる人もしくはやろうと思ってる人
- 会社でWebサービス・アプリやっていてどこでもいいからささっとコストカットしたい人
- そこまで潤沢な資産を持っているわけではない中小企業

# サービス想定

## ユーザ数

サービスのユーザ数・アクセス数です。
個人だしお金ないんで、当たり前ですが広告出したりとかそんなことはしてません。
SEOだけでここまでもっていきました。
ただそこまでのアクセス数ではないです。
~~でも前に会社で作ったサービスでもっとひどいのあったなぁ（・∀・）ﾆﾔﾆﾔ~~

|項目|1日あたり|
|--|--|
|UU|1500 ~ 2000|
|PV|20000 ~ 30000|

## 構成

サーバは基本的にCohonaで行っており、コンテンツの画像だけS3に保存して、対応前はS3にダイレクトにアクセスする形で画像サーバー代わりにしていました。
他でもS3をこういう風に使っているのがあったので、Image Proxyとかはお金ができたらやろうと思ってた。

結論から言うと、この構成は失策で画像サーバが落ちないという面ではいいのですが、S3とユーザ間の転送料（外部通信）がかさみます。
今回コストが高くなった原因がここで、S3にダイレクトにアクセスさせるとアクセスした分だけ転送料がかさみ利用者が増えるほど利益がマイナスになっていくという本末転倒な構成になります。

![infrastructure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9094/fd6081a8-c0bb-39aa-c528-1fd530f81d15.png)

# コスト

2月の途中で、S3の12ヶ月間の無料期間が切れて3月が対策前の金額になります。4月のあたまで対策したので、それ以降は対策後の金額です。

## Before

一ヶ月300ドルなので、対策しなかった場合は年間3600ドル（1ドル=100円換算でも36万円）になります

![Screen Shot 2022-12-11 at 2.07.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9094/afa2967d-913a-2a4f-4b09-f38415dd80cc.png)

## After

対策直後は新たに使用したLightsailが無料なので、有料になった7月のものを表示しています。

... (ﾟдﾟ)！ﾔﾍﾞｪ､ﾈﾀﾊﾞﾚｼﾀ

![Screen Shot 2022-12-11 at 2.08.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9094/1d6edf43-4253-bd72-3023-0e1846a604cf.png)


# 結論

>  **AWS Lightsail でImage Proxyを立てましょう！！**

[構成](#構成)でも述べたとおり、今回はAWSの転送料をいかに抑えるかが勝負になります。
外部との通信費だけでいうとLightsailはAWSのなかで最も安く、プランもそんなに大きなものでなければそこまでかかりません。
今回は毎月5USDのものを使用しました。

## Lightsailの転送料

下記にプラスして各プランごとに毎月、1TB以上転送料の無料枠があるので以下の料金はそれを超えた場合のみです

|1 か月あたりの料金|3.50 USD|5 USD|10 USD|20 USD|40 USD|80 USD|160 USD|
|--|--|--|--|--|--|--|--|
|データ転送|0.5 TB|1 TB|1.5 TB|2 TB|2.5 TB|3 TB|3.5 TB|

## S3の転送料

毎月100GBまでは無料ですが、画像サーバとしてS3を利用した場合、簡単にこれを上回ってしまうので、実質以下の料金が毎月かかると思ったほうがいいです。

|最初の 10 TB/月|0.114USD/GB|
|--|--|
|次の 40 TB/月|0.089USD/GB|
|次の 100 TB/月|0.086USD/GB|
|150 TB/月超|0.084USD/GB|

## 対策後の構成

### 全体構成

Light sailを間に立て、Image Proxyの役割をさせてます。

![infrastructure2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9094/c96ff8f6-1158-a2a5-0fdf-33fa878014c6.png)

### Lightsailの中身

Image Proxy本体はGoで自作して、前段にcaddyを置くようにしています。
自作と言っても基本的にはSDKでS3から画像を取得してそれを出力しているだけです。
image proxyはGithubにOSSのものが、たくさん上がってますが、URLのパスがそれに引きづられてしまうのと機能過多で今回はそこまで求めてないので、数時間でささっと作った簡単なものです。

[caddy](https://caddyserver.com/)を使っているのはSSL証明書を自動発行してくれるからで、ほぼそれ目的です。
（法人の有料サービスで同様のことする際はしっかりした証明書を発行しましょう）

```
version: '3.8'
services:
  imageproxy:
    build:
      context: ./services/imageproxy
      dockerfile: Dockerfile
    container_name: imageproxy
    environment:
      - LOG_LEVEL=info
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
    logging:
      options:
        max-size: "10m"
        max-file: "7"
    networks:
      - backend

  proxy:
    build:
      context: ./infrastructure/caddy
      dockerfile: Dockerfile
    container_name: proxy
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./infrastructure/caddy/config/imageproxy.json:/etc/caddy/config.json
      - ./infrastructure/caddy/data:/data
    networks:
      - backend

networks:
  backend:
```

#### Caddyの構成

caddyはImage Proxyの前段においていて、ほとんどSSL証明書発行・管理のためだけの存在なので特別なことはしていません

```
{
  "admin": {
    "disabled": true
  },
  "logging": {
    "logs": {
      "": {
        "writer": {
          "output": "stdout"
        },
        "encoder": {
          "format": "json"
        },
        "level": "INFO"
      }
    }
  },
  "apps": {
    "http": {
      "servers": {
        "proxy": {
          "listen": [
            ":443"
          ],
          "routes": [
            {
              "handle": [
                {
                  "handler": "reverse_proxy",
                  "transport": {
                    "protocol": "http"
                  },
                  "upstreams": [
                    {
                      "dial": "imageproxy:80"
                    }
                  ]
                }
              ],
              "match": [
                {
                  "host": [
                    "your domain"
                  ]
                }
              ]
            }
          ]
        }
      }
    }
  }
}
```

# 後記

会社で使ったことがあるからと言って同じノリで個人で使うのはやめましょう。