GCPで永久無料枠を利用してサービスを立ち上げたときにしたことの備忘録

# はじめに
最近GCPでWebサービスを立ち上げたので、そのときに実施したことをメモとして残しておきます。
今回はGCEで Debian + Nginx + Railsで環境を作りました。
ドメイン取得以外は終始無料で進めるための努力をしました。
また、今回はRailsアプリケーションを作成することは目的としていませんので、そこについてはあまり触れません。

# やったこと
## GCEでインスタンスを立ち上げる
アカウント作成時に貰える無料トライアル枠とは別に、無料で利用できるリソースがあります。
Always Free と呼ばれていて、GCEの場合は以下の要件を満たすインスタンスのみ永久に無料でインスタンスを立てることができます。

* リージョンをus-*1から選択する 
* 1つのf1-micro VM インスタンス
* 30GB以内 の永続ストレージ

> ※無料対象リージョンはus-*1のみというご指摘を受けましたので、上記修正しました。
https://cloud.google.com/free/docs/gcp-free-tier?hl=ja#always-free-usage-limits
@gayouさんコメントありがとうございました。

上記の設定なら永続的に無料で利用できます。
したがって画面の赤枠のところを上記のように設定します。
<img width="1288" alt="スクリーンショット 2019-03-18 0.48.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/255007/48713804-82e2-cb28-939b-ac362144e27d.png">

## 80,443ポートを解放する
上記のインスタンスの設定の下にfirewallルールの設定を行えるところがあるので、
HTTPトラフィックとHTTPSトラフィックを許可するにチェックをつけておきます。
<img width="609" alt="スクリーンショット 2019-03-18 0.49.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/255007/cd556c6e-7cf5-9a68-5039-538c12bb4b9b.png">

## IPアドレスを固定しておく
なにも設定もしていない状態だと外部IPアドレスはエフェメラルの状態になってしまっています。
エフェメラルは日本語で「短命な」という意味で、インスタンスが立ち上がるたびに違うIPアドレスに変わってしまいます。
IPをドメインに割り当てたい場合など、固定化しておかないと不便になります。
したがって、外部IPを固定IPに変更する必要があります。
インスタンスが動いていれば、固定のIPアドレスにしても無料で使えます。
> ※静的外部IPアドレスの予約は、未使用時で＄0.01（毎時）の管理コストが発生します。
https://cloud.google.com/compute/pricing#ipaddress
@lentigo07さんコメントありがとうございました。

<img width="1384" alt="スクリーンショット 2019-03-18 1.00.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/255007/f8dead6a-8797-e03b-4bb2-81b42b55e7e9.png">
そのためにまず、外部IPのプルダウンを選択し、IPアドレスを作成を選択しましょう。
![screenshot_184-1.png](https://qiita-image-store.s3.amazonaws.com/0/255007/dff510a1-d2b5-3d19-70b6-018fa4eab943.png)
その後必要な項目を入力していき、入力内容とIPアドレスが表示されていればOKです。
<img width="366" alt="スクリーンショット 2019-03-18 1.04.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/255007/c8fbc219-7b47-ae36-634c-33ec148c0413.png">


## SSHの設定
### ポート変更
ssh接続をするためのポート番号を変更しました。
デフォルトは22番ポートを利用しているとおもいますが、こちらはwell-knownポートなので、ポートスキャンを行われてしまい、課金対象になる可能性があります。
したがって、任意の違うポートにするために以下のように設定します。

####ファイアウォールルールを作り、ポートを解放する
まずはファイアウォールルールを新規に作成します。
ブラウザでGCPの画面を開き、
メニュー > VPCネットワーク > ファイアウォールルール
の順に選択します。
すると以下のような画面が開くと思うので上部のファイアウォールルールの作成を選択し、新規のルールを作成します。
<img width="978" alt="スクリーンショット 2019-03-27 19.10.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/255007/3f7af20b-865a-01b0-ef51-ad3d0174fce2.png">

作成画面ではだいたい以下の場所を設定してあげて任意のポートを解放させてあげるといいです。
僕はルールの名前にはdefaultの指定方法にならって
original-allow-sshという名前にしていますが、わかりやすい名前であればなんでもいいと思います。
ターゲットタグの名前は、できればどのポートをどういう目的で設定しているかをわかるようにしたほうがいいと思います。
僕はallow40022-serverのようにしています。※今回はサンプルで40022を利用していますが、実際に利用している訳ではないです。
<img width="909" alt="スクリーンショット 2019-03-27 19.15.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/255007/1efaff3b-a78e-630a-1f20-a7f6c4544c67.png">

ここまでやればポートの解放するルールの設定は完了です。

#### サーバにタグを付ける
上記でファイアウォールルールを設定しましたが、インスタンスにタグづけしていないため適用されていません。
したがってインスタンスにタグを付与する必要があります。

GCPのブラウザ画面から
メニュー > Compute Engine > VMインスタンス
の順に選択し、インスタンスの設定画面を開きます。
その後、以下のようにネットワークタグの欄に上記で設定したタグをタグ付けして保存すれば設定完了です。
<img width="572" alt="スクリーンショット 2019-03-27 19.28.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/255007/50c32fb6-ad2c-bb9f-5427-0a7fd6f49821.png">

これで先ほど設定したルールが適用された状態になります。

#### sshd_config のポート番号の設定をかえる
インスタンスのサーバーにログインします。
まだこの段階ではsshポートを変更していないので22で接続できます。
接続したらsshd_configを以下のように書き換えます。

```sshd_config:/etc/ssh/sshd_config
#Port 22
Port xxxx ←設定したポート番号
```
上記のように設定したら以下のコマンドで設定を反映します。

```sh
sudo systemctl restart sshd
```
ここまでやったらsshd_configの編集をしたターミナルは閉じずに、指定したポートでsshできるかを確認してください。確認できたら、OKです。

#### 22番ポートを閉じる
ファイアウォールルールにポートを解放する時にやったように今度は閉じるルールを設定します。
命名などは解放する時と同じように設定しました。
気をつける箇所としては、一致した時のアクションを「許可しない」にすることです。
<img width="541" alt="スクリーンショット 2019-03-27 19.37.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/255007/30c20929-0a10-3684-bae2-7e2ab6cde06e.png">
上記ルールを作成し、最後にインスタンスのネットワークタグの部分に、ポート開けたときと同様にdisallow22-serverタグを追加して保存したら設定完了です。

実際に22番でssh接続しようと試みてください。
できなくなっていると思います。


### 公開鍵認証
GCPを使っている場合にはgcloudでsshすれば標準で接続できるようになっていますが、gcloudを利用しない場合には以下のように公開鍵認証の設定をする必要があります。

```console
$ ssh-keygen -t rsa -f ~/.ssh/gcp_key -C rikushiru

$ chmod 400 ~/.ssh/gcp_key 

$ cat ~/.ssh/gcp_key.pub
   ~ 中身をコピー ~
```

公開鍵をコピーしたらブラウザでGCPを開いて、vmインスタンスを選択し、編集画面を開いて
下の画像の赤ワクの欄に公開鍵を追加します。
<img width="993" alt="スクリーンショット 2019-03-27 18.40.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/255007/317d5cf2-c340-b5b3-4b9c-46cc77a7f130.png">

ここまで設定したらログインできるか確かめます。

```console
$ ssh rikushiru@<IPアドレス> -i ~/.ssh/gcp_key
```
ログインできていれば完了です。

### sshでrootにlogin出来なくする
ssh経由でrootにログインできないようにします。

rootはどのサーバーにもあるアカウントのため総当り攻撃される危険性があります。
Debianはデフォルトがyesなので自分で設定したほうがいいです。

```sshd:/etc/ssh/sshd_config
PermitRootLogin no
```

> DebianでPermitRootLoginがデフォルトでyesだったのは1.5世代くらい前の話で、 stretch は標準でprohibit-passwordに設定されているそうです。
なので、総当りされる危険性はないので、心配しなくて大丈夫なようでした。 
@sugi_0000 さんありがとうございます！

### パスワード認証を切る
GCPを利用しているならばそこまで注意しなくても公開鍵認証を適用していると思いますが、もしパスワード認証が有効になっているのであれば、パスワード認証を拒否し、公開鍵認証に変更しましょう。
パスワード認証が有効の場合には、辞書攻撃される危険があります。

```sshd:/etc/ssh/sshd_config
PasswordAuthentication no
ChallengeResponseAuthentication no
```

### sshdのプロトコルを2にする
sshdのプロトコル1は脆弱性が発見されている上に、サポートされていないので、プロトコル2を使いましょう。

```:/etc/ssh/sshd_config
Protocol 2
```

### 認証の試行回数を制限する

```:/etc/ssh/sshd_config
MaxAuthTries 5
```

## Nginxの設定
### バージョンを見えなくする
HTTPのレスポンスのServerヘッダにwebサーバーがなにで、バージョンがいくつなのかがかいてあります。
そのままにしておくと、脆弱性を調べられてしまい、アタックされてしまう可能性があるので、バージョンだけでも表示されないように変更します。

```nginx:/etc/nginx/nginx.conf
http {
        ~
   server_tokens off;
        ~
}
```

## nginxでリバースプロキシの設定
ポート番号80、443の双方からアクセスを受け取っていますが、実際のrailsアプリケーションは[http://localhost:3000]()でrailsサーバーを起動させているので、ポート番号80もしくは443で受け取ったらlocalhost:3000にリダイレクトするようにリバースプロキシの設定をします。

nginxの設定ファイルを以下のように修正します。

```nginx:/etc/nginx/sites-enabled/default
  ~ 略 ~
server {
  listen 80 default_server;
  listen [::]:80 default_server;

  server_name ドメイン;

  location / {
                proxy_pass http://localhost:3000;
  }
  ~ 略 ~
}
```
上記のように設定したら、以下のコマンドでnginxの再起動を行いましょう。

```sudo systemctl reload nginx```
これで80番ポートでアクセスが来た場合に、localhost:3000に対してリバースプロキシするようになったと思います。
443の場合にも同様の処理でリダイレクト可能ですが、それはこの後説明するhttpリクエストを受けたらhttpsにリダイレクトする対応をした時の内容に記載していきます。

##Railsアプリケーションをサーバー内に設置
### git cloneしてアプリケーションをインスタンス内に作成
ここについてはあまり深く触れませんが、
ローカルで作成したアプリケーション等があれば、一旦gitにあげてGCEインスタンス内にsshした後で好きなところでgit cloneするといいと思います。
おそらく、rbenvやrubyのインストールとrailsのインストールが必要になると思いますが、その辺はよしなにやってください。
これは個人的にな好みになりますが、私はアプリケーションの設置箇所を/にappディレクトリをつくって/appにgit cloneしてきました。
なので,
/app/railsアプリケーション
みたいな配置でやっています。

###インスタンス立ち上げ時に自動的にrailsのサーバーが立ち上がるようにする
インスタンスを再起動するたびにrailsのサーバーを立ち上げるのでは非常に面倒で仕方がないので、インスタンスが立ち上がったら自動的にサーバーが立ち上がるように設定しておきます。

cdで/etc/systemd/system/に移動し、
そこに.service拡張子でファイルを作成します。
app名.serviceで私は作成しました。
そしたら[こちらのdebianのsystemdのページ](https://wiki.debian.org/systemd/Services)の内容にしたがって、設定していきます。
私の場合は以下のように設定しました。

```ini:/etc/systemd/system/app名.service
[Unit]
Description=My Service
After=network.target

[Service]
Type=simple
Restart=always
WorkingDirectory=/app/［アプリ名］/ #アプリを設置したdirectory
ExecStart=/usr/local/bin/rails server --environment production  #本番環境でサーバーを立ち上げるコマンド

[Install]
WantedBy=multi-user.target
```
上記記載後は

```$ systemctl daemon-reload```
で適用させておきましょう。
これで自動的にサーバーが立ち上げるようになったと思います。
試しにインスタンスをストップしてもう一回立ち上げてみてください。
先ほどのリバースプロキシの設定と合わさって、httpでアクセスすればRailsアプリケーションがブラウザに表示されているかと思います。

もし立ち上がっていないようでしたら、以下のコマンドも実行してみてください。

```console
$ systemctl enable myservice.service
$ systemctl start myservice.service
```

## SSL対応する
### Let's Encrypt で証明書を発行
SSL対応するにあたり証明書が必要となりますが、その証明書を無料で作成できるLet's Encryptを利用して、今回は無料でHTTPS化をしていきます。
#### Debian backports リポジトリの有効化
Let’s EncryptのクライアントソフトウェアであるCertbotを使用して証明書を作成していきますが、 Debian9用のCertbotパッケージは Stretch backportsリポジトリから取得することになります。そのため、 backportsリポジトリが有効になっていなければ、有効にしておく必要があります。

```sh:/etc/apt/sources.list
deb http://ftp.debian.org/debian stretch-backports main #なければ追加
```

```console
$ apt -y update
```

> ※ こちら動作未確認ですが、@henrich さんよりいただいたコメントによりますと、
certbotは現在backportsでなくても問題なく利用できるそうでした！
情報ありがとうございます！！
https://tracker.debian.org/pkg/python-certbot


#### Certbotインストール
それではCertbotをインストールします。

```console
$ apt -y -t stretch-backports install certbot
```
インストールできたら事前準備は完了です。
#### Certbot実行
それでは証明書を発行していきたいと思いますが、取得する前にサーバーを停止する必要があるので、Webサーバーを停止しておきましょう。
##### Webサーバの停止

```console
$ systemctl stop nginx
```
サーバーを停止させたら以下のコマンドで、取得したドメインに対して証明書を作成していきます。

```console
$ certbot certonly --standalone -d example.jp
```
初めて行う場合にはメールアドレスの設定や、利用規約への同意を求められますので、設定しておきましょう。

#### SSL/TLS サーバ証明書の取得完了
しばらく待って以下のように表示されていれば証明書作成は成功です。

```
IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/example.com/fullchain.pem. Your cert will
   expire on 2017-10-15. To obtain a new or tweaked version of this
   certificate in the future, simply run certbot again. To
   non-interactively renew *all* of your certificates, run "certbot
   renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
```
作成に成功した場合には以下のようにいくつかのファイルが生成されます。

#####サーバ証明書
/etc/letsencrypt/archive/ドメイン名 配下に保存されます。

- サーバ証明書（公開鍵） … certN.pem
- 中間証明書 … chainN.pem
- サーバ証明書と中間証明書の結合ファイル … fullchainN.pem
- 秘密鍵 … privkeyN.pem

#####シンボリックリンク
/etc/letsencrypt/live/ドメイン名 配下に保存されます。

- サーバ証明書（公開鍵） … cert.pem
- 中間証明書 … chain.pem
- サーバ証明書と中間証明書の結合ファイル … fullchain.pem
- 秘密鍵 … privkey.pem


### httpでリクエストが来たらhttpsにリダイレクトするように設定する。
無事にssl証明書を取得できたところまで完了したら、httpsのみでアプリを起動させる方が良いので、httpでアクセスを受け取ったらhttpsにリダイレクトするように設定を変更しましょう。

リバースプロキシの設定を行ったファイルに対して以下のように修正を行います。

```nginx:/etc/nginx/sites-enabled/default
  ~ 略 ~
server {
  listen 80 default_server;
  listen [::]:80 default_server;

  server_name ドメイン;
    return 301 https://$host$request_uri; #受け取ったpathやhostを引き継いだ状態でhttpsでリダイレクトする
 
  #削除# location / {
    #削除#   proxy_pass http://localhost:3000;
  #削除# }
  ~ 略 ~
}
```
上記のように修正した状態で同じディレクトリ内に新しい任意のファイルを作成します。
そしてhttpsでアクセスを受け取った時のリバースプロキシの設定とssl証明書の設定を記載します。

```nginx:/etc/nginx/sites-enabled/ssl
server {
  listen 443 ssl default_server;
  listen [::]:443 ssl default_server;

  ssl on;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    # let's encryptで作成した証明書の設定
    ssl_certificate     /etc/letsencrypt/live/ドメイン名/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ドメイン名/privkey.pem;

  server_name ドメイン;

  location / {
                proxy_pass http://localhost:3000;
  }
}
```
ここまでやったらnginxを再起動しましょう

```console
$ sudo systemctl reload nginx
```
おそらく、httpでアクセスすると強制的にhttpsにリダイレクトされるようになっていると思います。


## Let's Encryptの証明書を自動更新するクーロンの設定
Nginxを使用している場合、 TCP Port Listenの関係でサーバーを停止する必要があるので、以下のように設定します。

```:/etc/cron.d/certbot
# /etc/cron.d/certbot: crontab entries for the certbot package
#
# Upstream recommends attempting renewal twice a day
#
# Eventually, this will be an opportunity to validate certificates
# haven't been revoked, etc.  Renewal will only occur if expiration
# is within 30 days.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 */12 * * * root certbot -q renew --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"
```
--pre-hook 証明書が実際に取得・更新できる場合に実行
--post-hook 証明書を取得・更新する試みが行われた場合に実行

もしcronを設定しない場合は90日を迎える前に手動で以下のコマンドを実行する必要があるようです。

```console
$ certbot renew
```
# スナップショットの作成
Google Compute Engine（GCE）では標準のバックアップツールとしてスナップショット機能が提供されています。
作成方法はすごく簡単で、ブラウザ上でぽちぽちするのでもいいですが以下のコマンドで一発で作成できます。

```console
$ gcloud compute disks snapshot インスタンス名 --zone=ゾーン名 --snapshot-names=スナップショット名

ex) gcloud compute disks snapshot rikushiru_instance --zone=us-west1-b --snapshot-names=https-active
```
インスタンスが万が一壊れてしまったときにすぐに復旧できるようにスナップショットを作成しておきましょう。
5GBまでは無料でスナップショットを保存出来ます。

また、スナップショットの特徴ですが、
1回目に作成したスナップショットでは、インスタンスのデータをすべてバックアップします。
2回目に作成されるスナップショットでは、差分データのみがバックアップされます。
初めて作成したスナップショットを削除すると、2回目に作成したスナップショットのデータに1回目のスナップショットのデータが引き継がれるようになります。



# やるつもり
## bastionサーバーの作成
bastionサーバーを作る場合には以下の設定をする予定。

- 特定のIPからのみログインを許可する。
- デフォルトのポート番号(22)から変更する。

###ファイアウォールルールの設定

```bash:
gcloud compute firewall-rules create "allow-ssh-bastion" \
    --allow tcp:任意のport番号,tcp:22 --target-tags bastion \
    --description "踏み台サーバー(特定のIPからのみSSH接続を許可)" \
    --priority=1000 \
    --source-ranges "固定IP/32" \
    --network=sample
```

### bastionサーバの作成
OS等はなんでもいいので、bastion1みたいな名前でf1-microでインスタンスをたちあげて、上記で設定したファイアウォールルールを指定します。

```console
$ gcloud compute instances add-tags bastion1 --tags bastion
```

### SSHポート番号変更
最後にsshdのport番号を変更します。

```sshd:/etc/ssh/sshd_config
#Port 22
Port 変更したポート番号
```
最後にsshdを反映させて終わりになる予定です。

```console
$ sudo systemctl restart sshd
```


# 追記
@mattn さんより教えていただきました件について記載します。
GCPは中国とオーストラリアからのアクセスは課金対象になるそうです。
そのため上記アクセスからアタックを受けると数円課金が発生してしまうようです。
https://cloud.google.com/free/docs/always-free-usage-limits

私は対応していませんが、@mattn さんは以下のように対応されているようです。
以下から得られる IP アドレスを全てブロック。

- https://ipv4.fetus.jp/cn.txt
- https://ipv4.fetus.jp/au.txt

その上で 
fail2ban を仕掛けて数回アタックがあった場合にBANを実行。
そこまでやると毎月0円～2円くらいになるそうです。

ご指摘ありがとうございます!!