GitHub Actionsで The `set-env` command is disabled. のエラーが出た時の対応

GitHub Actionsのworkflowでエラー発生

あるRailsプロジェクトのテストをGitHub Actionsで実行していますが、突然即終了になるようになってしまいました。

見てみると、Rubyのセットアップのところで以下のエラーが出ていました

1
Error: The `set-env` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/

Rubyのセットアップ自体は

1
2
- name: Set up Ruby
uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0

という感じのコードになっていました。

エラーの原因調査

特になにも変更していないのになと思ったのですが、まずはエラーにあるリンクのドキュメントを読んでみました。

2020年10月なので、結構前からのようですね。ざっくりいうとセキュリティの問題からset-envadd-pathというコマンドは使えなくなりましたということのようです。どうしても使いたい場合はACTIONS_ALLOW_UNSECURE_COMMANDSという環境変数をtrueに設定すればよいようですが、のぞましくはないんでしょうね。

しかし、見たところset-envadd-pathも使っていません。どこかで呼び出されているのでしょうか。

考えられるのはruby/setup-rubyというActionの中で利用しているということです。

githubをみてみましょう。
https://github.com/ruby/setup-ruby

このREADMEをみると、ruby/setup-ruby@v1となっています。こちらで現在設定しているのはruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0です。このcommit idのようなバージョンはGitHub Actionsのテンプレートで指定されていたバージョンです。

試しに今テンプレートを見てみました。

1
2
3
4
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
# change this to (see https://github.com/ruby/setup-ruby#versioning):
# uses: ruby/setup-ruby@v1
uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e

というコードになっており、workflowを作成するタイミングでテンプレートが指定するコミットも変わるようです。コメントにあるように、@v1にしておくのが良さそうです。

というわけで、ruby/setup-ruby@v1に変更することでdeprecationエラーも表示されなくなりました。

まとめ

deprecationエラーの場合は、基本的にはバージョンアップで対応する。コミット指定だと対応が自動的に対応はできないので、バージョン指定が望ましい。自分がソフトウェアを公開する時にも参考になりますね。

RailsのRestClientが利用するTLSのバージョンを確認する

TLSv1.2への移行

利用しているAPIがTLSv1.2へ移行するとのことで、クライアント側がTLSv1.2で通信可能かどうか確認してくださいと連絡をもらいました。

APIにアクセスしているのはrest-client(2.1.0)でした。なのでrest-clientがアクセスする際に利用するTLSのバージョンを確認すればよいことになります。

TLSのバージョン取得方法

ググってみるとopensslのドキュメントが見つかりました

https://www.openssl.org/docs/man1.1.1/man3/SSL_get_version.html

いろんな種類があるようです。

rest-clientの場合

rest-clientはリクエストを作成する場合にNet::HTTPを利用します。

https://github.com/rest-client/rest-client/blob/master/lib/restclient/request.rb#L163
https://github.com/rest-client/rest-client/blob/master/lib/restclient/request.rb#L464

Net::HTTPが利用するTLSのバージョンを知るためにgithubでコードを確認してみます。

https://github.com/ruby/net-http/blob/master/lib/net/http.rb#L975

https://github.com/ruby/net-http/blob/master/lib/net/http.rb#L1030

ここでsocketの生成を行なっています

https://github.com/ruby/net-http/blob/master/lib/net/http.rb#L1044

@socketを作成しています。Net::BufferedIO.newの引数で先ほど作成したsocketを指定しています。

BufferedIOを確認すると、#ioというメソッドがあり、オブジェクトを返してくれるということです。

Module#prependで試してみる

Module#prependを使って実際に出力してみます。

処理を入れるところはsocketをクローズする手前のdo_finishメソッドにします。

https://github.com/ruby/net-http/blob/master/lib/net/http.rb#L1069

まずは@socketを確認してみます。

1
2
3
4
5
6
7
$ rails c
[1] pry(main)> Net::HTTP.prepend Module.new { def do_finish; pp @socket; super; end }
=> Net::HTTP
[2] pry(main)> RestClient.get('https://google.co.jp')
#<Net::BufferedIO io=#<OpenSSL::SSL::SSLSocket:0x00007f81b4001558>>
#<Net::BufferedIO io=#<OpenSSL::SSL::SSLSocket:0x00007f81aedd4be0>>
=> <RestClient::Response 200 "<!doctype h...">

ioというOpenSSL::SSL::SSLSocketクラスのインスタンスがあります。
ioの中身を確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[1] pry(main)> Net::HTTP.prepend Module.new { def do_finish; pp @socket.io; super; end }
=> Net::HTTP
[2] pry(main)> RestClient.get('https://google.co.jp')
#<OpenSSL::SSL::SSLSocket:0x00007f8a3a8f9b18
@context=
#<OpenSSL::SSL::SSLContext:0x00007f8a3a8fac48
@cert_store=
#<OpenSSL::X509::Store:0x00007f8a369b2ba8
@chain=nil,
@error=nil,
@error_string=nil,
@time=nil,
@verify_callback=nil>,
@max_proto_version=nil,
@min_proto_version=769,
@session_new_cb=
#<Proc:0x00007f8a3a8f9cf8@/Users/s1180997/.anyenv/envs/rbenv/versions/2.6.6/lib/ruby/2.6.0/net/http.rb:986>,
@verify_hostname=true,
@verify_mode=1>,
@eof=false,
@hostname="www.google.co.jp",
@io=#<TCPSocket:fd 26, AF_INET, 10.81.20.99, 55387>,
@rbuffer="",
@sync=true,
@sync_close=true,
@wbuffer="">

OpenSSL::SSL::SSLContextのインスタンスの@contextから取得できそうです。

1
2
3
4
5
6
7
8
[1] pry(main)> Net::HTTP.prepend Module.new { def do_finish; pp @socket.io.context.ssl_version; super; end }
=> Net::HTTP
[2] pry(main)> RestClient.get('https://google.co.jp')
NoMethodError: undefined method `ssl_version' for #<OpenSSL::SSL::SSLContext:0x00007ff13076a998>
from (pry):1:in `do_finish'
Caused by NoMethodError: undefined method `ssl_version' for #<OpenSSL::SSL::SSLContext:0x00007ff1310a2808>
Did you mean? ssl_version=
from (pry):1:in `do_finish'

ん〜ダメでした。setterのみのようです。

ためしにmethodメソッドを利用してみます。

1
2
3
4
5
6
[1] pry(main)> Net::HTTP.prepend Module.new { def do_finish; pp @socket.io.method(:ssl_version); super; end }
=> Net::HTTP
[2] pry(main)> RestClient.get('https://google.co.jp')
#<Method: OpenSSL::SSL::SSLSocket#ssl_version>
#<Method: OpenSSL::SSL::SSLSocket#ssl_version>
=> <RestClient::Response 200 "<!doctype h...">

ssl_versionがかえってきています。ということは呼び出せるということですね。

1
2
3
4
5
6
[1] pry(main)> Net::HTTP.prepend Module.new { def do_finish; pp @socket.io.ssl_version; super; end }
=> Net::HTTP
[2] pry(main)> RestClient.get('https://google.co.jp')
"TLSv1.3"
"TLSv1.3"
=> <RestClient::Response 200 "<!doctype h...">

無事バージョンが取得できました!

まとめ

RailsのRestClientで利用しているTLSのバージョンを確認するにはrails consoleで

1
Net::HTTP.prepend Module.new { def do_finish; pp @socket.io.ssl_version; super; end }

を実行してからRestClient.getでリクエストを送る。

fluentd-plugin-concatのtimeout_labelについて

前回までのまとめ

前回、分割されたdockerのログをどのようにして結合したかを記載しました。

今回はfluentd-plugin-concatの設定で用いたtimeout_labelについて記載します。

timeoutエラーが大量に発生する

最初はtimeout_labelを設定していませんでした。それでも正しく動作したのですが、CloudWatchのログを確認すると、大量のERRORが発生していました。

1
2
Throughput limits for the delivery stream may have been exceeded.
294 records failed to be delivered. Will retry.

結合されたログが正しくkinesisに出力されていなかったようです。

timeout_labelを追加する

timeout_labelを設定するとログを再ルーティングできると書いてあったので、ログを結合する箇所に timeout_labelを設定しました。そのlabelの先では、通常(タスク定義ファイルで設定した)通り、kinesisへデータを送るように設定します。

設定した箇所のconfを掲載します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<filter firelens**>
@type concat
key log
stream_identity_key partial_id
multiline_start_regexp /{"JSONの先頭":/
multiline_end_regexp /"JSONの終点"}$/
separator ""
timeout_label @TIMEOUT
</filter>

<label @TIMEOUT>
<match **>
@type kinesis_firehose
delivery_stream_name kinesis-data-stream
region ap-northeast-1
</match>
</label>

timeout_labelの設定をしなければ、filterディレクティブだけで設定は完了です。実際に転送するkinesisの設定は、タスク定義ファイルで行うからです。

しかし、今回timeout_labelの設定を行なって、その転送先として、タスク定義ファイルと同じ設定をしています。

これでtimeout_labelに引っかかったログは、正しい転送先へ送られます。

timeout_labelに引っかかったということはCloudWatchを見ると確認できます。

1
adding match in @TIMEOUT pattern="**" type="kinesis_firehose"

まとめ

Dockerのログが16kで切れてしまう問題について、記載してきました。fluentd-plugin-concatの設定が実際のログデータに依存してしまっているので、その辺りをDockerのログであるという普遍的なデータ(partial_idやpartial_lastなど)を用いて結合できるように設定できるとよりスマートかなと思います。今後はその辺りも調査していきたいと思います。

分割されたログを結合する方法

前回までのまとめ

前回、dockerのログが16kで分割されてしまう原因がdockerのログドライバの制限のためだったというところまで記載しました。

今回は分割されたログをどのようにして結合したかを記載したいと思います。

そういえば、分割されるとなぜ困るのかを記載してなかったかもしれません。なぜ困るかというと、ログはJSONで、分割されると妥当なJSONではなくなってしまうからです…

結合する方法

試してみた方法を2つ紹介します。

lambdaで結合する

最初はKinesis Data Firehoseで設定したLambdaで結合しようと試みました。

前回に少し紹介した、ログに含まれる分割されたかどうかを示す情報を元に、結合する場合は改行コードを追加せず、JSONの終端の場合は改行を追加するようにしました。

Lambdaはこんな感じになりました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
exports.handler = async (event, context) => {
/* Process the list of records and transform them */
//console.log(JSON.stringify(event, null, 2));
const output = event.records.map((record) => {
const payload = Buffer.from(record.data, 'base64').toString('ascii');
const payload_json = JSON.parse(payload);
let d = payload_json["log"];
if (add_line_feed(payload_json) === true) {
d = `${d}\n`;
}
const buf = Buffer.from(d);

/* This transformation is the "identity" transformation, the data is left intact */
return {
recordId: record.recordId
result: 'Ok',
data: buf.toString('base64')
}
});
console.log(`Processing completed. Successful records ${output.length}.`);
return { records: output };
};

function add_line_feed(obj) {
if (obj.hasOwnProperty("partial_message") === false) {
return true;
}
if (obj.hasOwnProperty("partial_message") === true && obj["partial_last"] === "true") {
return true;
}
return false;
}

改行を付与する(=結合するかどうか)条件は関数にまとめてみました。このように行うことで、ログは適切に結合されました。

Lambdaで結語することの問題点

無事ログを結合することができたのですが、Kinesisでバッファリングしたデータを出力するタイミングが必ずしもログを結合したあととは限りません。

ですので、なにが起こるのかというと、S3に保存されるファイルは、約1/2の確率で、分割されたログで終わってしまっているのです。

これでは解決になっていません。Kinesisに渡される前に分割されたデータを結合しないといけないということがわかりました。

代替案の検討

Kinesisに渡される前となると、FireLensで出力されたときには結合されていないといけません。つまり、FireLensで結合しないといけないということになります。

FireLensは、以下のクラスメソッドさんの記事を参考に設定しました。

https://dev.classmethod.jp/articles/ecs-firelens/

ここでは、FireLensのコンテナイメージとして、AWSが提供するaws-for-fluent-bit:latestを利用しています。

しかし、fluent-bitにはログを結合するプラグインはこの記載時点では存在していませんでした。fluentdにはログ結合のためのプラグインが存在していました。

よって、fluentdを使ってログの結合を行おうと思います。

fluentdで結合

fluentdで受け取ったログを結合するにはconcatプラグインを利用します。また、Kinesis Data Firehoseと接続するためにkinesis接続用プラグインも利用します。

concatプラグインはこちら

https://github.com/fluent-plugins-nursery/fluent-plugin-concat/

Kinesisのプラグインはこちら

https://github.com/awslabs/aws-fluent-plugin-kinesis

fluentdのイメージ

ではまずfluentdでログを結合するためにfluentdのベースイメージを探そうと思います。fluent-bitはAWS推奨のイメージがありましたが、fluentdにはないようです。ですので、公式のイメージをベースイメージとしました。

Dockerfileは以下になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FROM fluent/fluentd:v1.9.1-1.0

USER root

RUN apk add --no-cache --update --virtual .build-deps \
sudo build-base ruby-dev \
# cutomize following instruction as you wish
&& sudo gem install fluent-plugin-concat \
&& sudo gem install fluent-plugin-kinesis \
&& sudo gem sources --clear-all \
&& apk del .build-deps \
&& rm -rf /home/fluent/.gem/ruby/2.5.0/cache/*.gem

COPY fluent.conf /fluentd/etc/fluent-custom.conf
COPY entrypoint.sh /bin/
RUN chmod +x /bin/entrypoint.sh

USER fluent

1箇所、不思議な点があります。

1
COPY fluent.conf /fluentd/etc/fluent-custom.conf

こちらですが、fluent.confではなくfluent-custom.confとしています。なぜかというと、fluent.confはECS FireLensで自動的に上書きされてしまうため、ファイル名を変更してfluentdのホスト内に置いておきます。

fluent-custom.confの内容は以下になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<filter firelens**>
@type concat
key log
stream_identity_key partial_id
multiline_start_regexp /{"JSONの先頭":/
multiline_end_regexp /"JSONの終点"}$/
separator ""
timeout_label @TIMEOUT
</filter>

<label @TIMEOUT>
<match **>
@type kinesis_firehose
delivery_stream_name kinesis-data-stream
region ap-northeast-1
</match>
</label>

<label @TIMEOUT>については、また次回紹介します。

プラグインの設定のみ行なっています。sourceディレクティブを入れると、

1
unexpected error error_class=Errno::EADDRINUSE error="Address in use - bind(2) for 127.0.0.1:24224"

と表示されてエラーになってしまいます。この辺りはECSが自動的に設定してくれるので、必要最小限の設定で動作します。

こちらでビルドし、ECRにpushします。

タスク定義ファイルの修正

ECSのタスク定義ファイルを変更します。以前はfluent-bitを利用していたので、fluentdに変更する必要があります。

イメージの変更

まずはイメージを変更します。

1
2
3
4
5
6
7
8
9
     {
"name": "log-router",
- "image": "906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:latest",
+ "image": "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/fluentd-for-docker-log:1.0.0",
"essential": true,
"firelensConfiguration": {
- "type": "fluentbit"
+ "type": "fluentd"
},

ここで一旦デプロイしてみましたが、エラーが発生しました。

1
Unable to generate firelens config file: unable to generate firelens config: unable to apply log options of container ap to firelens config: missing output key @type which is required for firelens configuration of type fluentd

ログ出力の設定をfluent-bit形式からfluentd形式に変える必要があります。

FireLensをログドライバとして指定するアプリケーションのログの設定を以下のように変更します。

1
2
3
4
5
6
7
8
"logConfiguration": {
"logDriver": "awsfirelens",
"options": {
"@type": "kinesis_firehose",
"region": "ap-northeast-1",
"delivery_stream_name": "kinesis-data-stream"
}
},

fluent-bitの場合は"Name": "firehose""delivery_stream": "kinesis-data-stream"でしたが、fluentdの場合は上記のように設定する必要があります。この辺りのfluent-bitとfluentdの違いについてはタスク定義ファイルで吸収してくれるとありがたいのですが、そうではないようです。

この辺りの設定は以下のページを参考にしました。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/using_firelens.html

最後にgithub actionsでデプロイして無事ログが結合された状態でKinesis Data Firehoseに渡ることが確認できました。S3上でも分割されていないことが確認できました。

まとめ

fluent-bitにはconcatプラグインがなかったのと、AWS推奨のfluentdイメージがなかったので、fluentd公式のイメージをベースにイメージをビルドしました。

タスク定義ファイルでもfluent-bitとfluentdの違いに少し戸惑ったのと、ECSならではの自動化された設定をこちらで準備してはいけないということころで、少しハマりました。

次回はflunet.confにあるTIMEOUTラベルについて記載したいと思います。

Dockerのログが16Kで分割されてしまう制限について

背景

ブラウザからデータを受け取り、それをS3に保存するアプリケーションの構築をAWS ECS Fargateで行いました。

フロントはnginx + nodejsを利用しています。フロントはブラウザから受けとったデータを加工して標準出力に出力するだけの役割です。

データはログルーターのFireLensを経由して、Kinesis Data Firehoseに送られ、LambdaをS3に保存するようにしました。

構成は上記の画像のようになります。

実際にデータを送ってみると、S3に保存された1つのJSONが途中で分割され2行になってしまっていました。

原因の調査

Lambdaでの調査

Kinesis Data Firehoseから起動しているLambdaでは、FireLensから送られてくるデータの必要な箇所(logというキーで取得できるログ本体)のみを取得し、改行をつけるだけの処理を行なっていました。(Kinesisからのデータなので、Base64デコードエンコードは行なっています)

なぜ分割されるのかがわからないのですが、なにか法則性がないかを調査したところ、分割されたデータの前半の長さは必ず16384バイト(16K)ということがわかりました。

しかも、Kinesis Data Firehoseに送られてくるデータには、データ本体のlog以外にも、partial_idpartial_messagepartial_lastなどのキーが含まれていました。

つまり、Kinesis Data Firehoseに入ってくる前にデータは分割されていたということです。

コンテナの調査

ECS Fargateで出力したデータを調査しました。nodejsアプリケーションから出力するデータをCLoudWatchで見てみると、その段階ですでに分割されていました。

ということはFireLensが原因ではないようです。

原因はDockerのロギングドライバの制限

調査の結果、原因はDockerロギングドライバの制限でした。1行あたり16Kが上限になっていて、現時点では変更できないようです。

https://github.com/moby/moby/issues/32923

まとめと次回予告

Dockerのロギングドライバの制限によってデータが16Kを超えると分割されてしまうということがわかりました。調べても意外と載っていないので、原因がわかるまで時間がかかりました。

次回はこの問題についてどう対応したかを記載したいと思います。

hexoアプリケーションの設定

前回までのまとめ

前回、新しいhexoアプリケーションのドメインの設定、nginxの設定、let’s EncryptでのSSLの設定を行いました。

今回は、既存のhexoのバージョンアップを行う予定でしたが、既存と新規のhexoは完全に独立しているので、既存のhexoはそのままでも大丈夫そうです。

なので今回は、hexoをsystemdを使って起動するようにするのと、hexoの細かな設定を行って、サイトが常時表示されるようにしようと思います。

systemdの設定

以前、systemdの設定でハマったことを書きました。これを参考に進めていきます。メモしておくと便利ですね!

unitファイルの追加

以前作成したファイルを少し変更すれば利用できそうです。ファイルをコピーします。

1
$ sudo cp /etc/systemd/system/hexo.service /etc/systemd/system/business_book_reviews_hexo.service

WorkingDirectoryなどを変更します。変更した結果は以下です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ diff -u /etc/systemd/system/hexo.service /etc/systemd/system/business_book_reviews_hexo.service
--- /etc/systemd/system/hexo.service 2020-05-07 01:21:21.271546129 +0900
+++ /etc/systemd/system/business_book_reviews_hexo.service 2020-12-11 01:12:18.544145188 +0900
@@ -1,11 +1,11 @@
[Unit]
-Description=hexo
+Description=business_book_reviews_hexo
After=syslog.target network.target

[Service]
Type=simple
-ExecStart=/usr/bin/sudo /home/ec2-user/.anyenv/envs/nodenv/shims/hexo server -p 8080
-WorkingDirectory=/home/ec2-user/blog
+ExecStart=/usr/bin/sudo /home/ec2-user/.anyenv/envs/nodenv/shims/npx hexo server -p 8081
+WorkingDirectory=/home/ec2-user/business-book-reviews
StandardOutput=syslog
StandardError=syslog
KillMode=process

systemdの登録確認

unitファイルが追加されたか確認します。

1
2
3
$ sudo systemctl list-unit-files --type=service | grep hexo
business_book_reviews_hexo.service disabled
hexo.service enabled

systemdで起動する

今はdisableになっているのでenableにします

1
2
$ sudo systemctl enable business_book_reviews_hexo
Created symlink from /etc/systemd/system/multi-user.target.wants/business_book_reviews_hexo.service to /etc/systemd/system/business_book_reviews_hexo.service.

起動します

1
$ sudo systemctl start business_book_reviews_hexo

プロセスを確認します

1
2
3
$ ps -ef | grep npx
root 10987 1 0 01:17 ? 00:00:00 /usr/bin/sudo /home/ec2-user/.anyenv/envs/nodenv/shims/npx hexo server -p 8081
ec2-user 11034 10660 0 01:17 pts/0 00:00:00 grep --color=auto npx

大丈夫そうですね。画面を確認します。

hexo初期設定起動確認

大丈夫そうですね!

hexoの設定

サイトの設定

アプリケーションをbusiness.book-reviews.blogにします。_config.ymlを編集します。

差分は以下になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 # Site
-title: Hexo
-subtitle: ''
+title: business.book-reviews.blog
+subtitle: ビジネス書の読書ブログ
description: ''
keywords:
-author: John Doe
-language: en
-timezone: ''
+ - ビジネス書
+ - 書評
+author: Motoaki Shibagaki
+language: ja
+timezone: Asia/Tokyo

subtitlekeywordsも設定しましたが、TOPページには反映されないようです。

URL設定

以前、URLから日付を削除するという記事を書きました。それと同じ対応を行います。

編集した結果は以下です。

1
2
3
4
5
-url: http://example.com
+url: https://business.book-reviews.blog
root: /
-permalink: :year/:month/:day/:title/
+permalink: :title/

動作確認

再起動して動作確認を行います。まず再起動します。

1
$ sudo systemctl restart business_book_reviews_hexo

サンプル記事をクリックして表示します。

サンプル記事の表示

いい感じですね!

まとめ

systemdの設定を行い、常時起動するようにしました。また、hexoの最低限の設定を行いました。

次回は記事の引っ越しを行いたいと思います。Google Search ConsoleでURLの削除、GAの設定なども行いたいと思います。

hexoアプリケーションのWebサーバ・SSLの設定

前回までのまとめ

前回、hexoアプリケーションのテーマを決めました。今回は細かい設定とサーバー側の設定を行っていきます。

ドメインの設定

リクエストを受け付けるサブドメインを作成します。一旦、business.book-reviews.blogとします。サブドメインの設定は契約しているバリュードメインの管理画面で行います。

久々見ると忘れてしまっていて、どこからDNS設定を行っていいかわかりません…しばらく探したあと、左メニューのドメインをクリックするとページが遷移します(フォーカスをあてた後のメニューではありません)。その中にドメインの設定操作があり、その中にDNSレコード/URL転送の設定があります。対象のドメインを選択すると、大きなテキストボックスがあり、その中にDNSレコードを入力します。

現状、

1
a * 18.182.54.190

となっているため、どのサブドメインでもアクセスできます。これを機に限定して設定します。

1
2
a book-reviews.blog. 18.182.54.190
a business.book-reviews.blog. 18.182.54.190

TTLが切れた後にdigコマンドで確認します。

1
2
3
4
5
6
7
8
$ dig book-reviews.blog a
(省略)
;; QUESTION SECTION:
;book-reviews.blog. IN A

;; ANSWER SECTION:
book-reviews.blog. 113 IN A 18.182.54.190
(省略)

ちゃんと名前は引けています。新しく追加したドメインも確認します。

1
2
3
4
5
6
7
8
$ dig business.book-reviews.blog a
(省略)
;; QUESTION SECTION:
;business.book-reviews.blog. IN A

;; ANSWER SECTION:
business.book-reviews.blog. 152 IN A 18.182.54.190
(省略)

大丈夫そうですね。念のため、設定していないドメインで名前が引けないことも確認しておきます。

1
2
3
4
5
6
7
8
$ dig test.book-reviews.blog a
(省略)
;; QUESTION SECTION:
;test.book-reviews.blog. IN A

;; AUTHORITY SECTION:
book-reviews.blog. 120 IN SOA ns1.value-domain.com. hostmaster.book-reviews.blog. 2020120716 3600 900 604800 120
(省略)

名前が引けないことを確認しました。

これでドメインの設定は完了です。

nginxの設定

次にnginxの設定を行います。
最低限の設定のみ行います。

1
2
3
4
5
6
7
server {
server_name business.book-reviews.blog;

location / {
proxy_pass http://localhost:8081/;
}
}

設定を繁栄します。

1
$ sudo systemctl reload nginx

反映されたことを確認します。ブラウザでhttp://business.book-reviews.blogにアクセスして、502 BadGatewayがかえってくることを確認します。

確認できたので、次はSSLの設定を行います。

SSLの設定

以前let’s encryptの導入を行ったページを参考に設定を行っていきます。

インストールはされているので、CertBotの実行から行います。

1
2
3
4
5
6
7
8
9
10
11
$ sudo certbot --nginx
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx

Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: book-reviews.blog
2: business.book-reviews.blog
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 2

今回はドメインが2つあるので、選択肢が2つ出てきます。2を選んで実行します。

1
2
Congratulations! You have successfully enabled
https://business.book-reviews.blog

無事HTTPSが有効になりました。

nginxの設定で、proxy_set_headerがまるっと抜けていたので追記してリロードします。

追記した内容

1
2
3
4
5
proxy_set_header    Host    $host;
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;

ブラウザでhttps://business.book-reviews.blogにアクセスし、同じように502 BadRequestが返ってくることが確認できました。

まとめ

新しいhexoアプリケーションのWebサーバ、SSLの設定を行いました。

次回は、新しくインストールするhexoアプリケーションと既存のhexoアプリケーションのバージョンの差が機になるので、既存のhexoのバージョンアップを行いたいと思います。

docker-composeで複数のRailsローカル開発環境を作成する

背景

今は一つのRailsローカル開発環境を使って開発を行なっています。ところが、別のRailsアプリケーションの開発を行うことになりました。なので、もう一つのRailsローカル開発環境を作成する必要が出てしまいました…そのときの作成メモを記載します。

ローカル開発環境の構成

既存の開発環境は以下の2つの要素でできています

  • docker-compose
  • docker-sync

それぞれ、なぜ利用するかなど確認してきましょう。

コンテナ間通信

RailsアプリケーションはRailsが動作するアプリケーションコンテナとMySQLが動作するDBコンテナが協調して動作することで成り立ちます。

複数のコンテナを通信させる場合、2つの方法があります。一つは--linkオプションを使う方法、もう一つはdockerネットワークを利用する方法です。

–linkオプションを利用する方法

詳しくはこちらに記載があります。このサイトによると、--linkオプションはレガシー機能で、将来的に削除される可能性があるということです。

dockerネットワークを利用する方法

dockerネットワークを利用する方法もこちらに記載されています。ネットワークを作ることで、コンテナ同士がコンテナ名で通信が行えるようになります。

dockerネットワークの作成を個別で行なってもよいですが、もっと便利な方法があります。docker-composeを利用することです。

docker-compose

docker-composeについてもさくらのページに説明があります。

docker-composeはdocker-compose.ymlというファイルに構成を記載します。YAMLの最初にバージョン番号を記載するのですが、今のバージョンはいくつなんだろうと思って調べてみると、以下のページが見つかりました。

https://docs.docker.com/compose/compose-file/compose-versioning/

マイナーバージョンもあったんですね…知りませんでした。既存で使っているdocker-composeのversionは3です。

docker-sync

ローカル開発環境を構成するもう一つの要素はdocker-syncです。

docker-syncを使う理由

docker-syncを使う理由としては、すでに詳しい記事がいくつかあります

docker-syncでホスト-コンテナ間を爆速で同期する

上記の記事の中に、docker-sync以外の方法も記載されていました

Docker for Mac の Mutagen-based caching で Volume のパフォーマンスが劇的に改善した

現状ですと、docker-sync以外にvolumeのパフォーマンスを改善する方法がないようです。それがdocker-syncを使う理由だと思います。

docker-syncの仕組み

仕組みについても先ほどの記事に記載がありました。

docker-syncの仕組みとしては、 docker の -v で直接ホスト側のディレクトリをマウントするのではなく、rsyncやunisonの仕組みでホスト側のファイルをコンテナ側に転送しよう、というものです。

なるほど、ファイル転送専用のコンテナを準備して同期を取るということですね。

設定に関しては、docker-sync.ymlで行います。

既存のローカル開発環境の設定

既存のアプリケーションの設定ファイルを確認します

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
version: '3'

services:
db:
image: mysql:5.6
environment:
MYSQL_ROOT_PASSWORD: password
ports:
- '3306:3306'
volumes:
- mysql:/var/lib/mysql
- ./docker/mysql/conf.d:/etc/mysql/conf.d
- ./docker/mysql/init:/docker-entrypoint-initdb.d

web:
build:
context: .
dockerfile: ./docker/rails/Dockerfile
command: ['rails', 's', '-p', '3000', '-b', '0.0.0.0']
ports:
- "3000:3000"
volumes:
- rails-sync-volume:/var/www/html:nocopy
- bundle:/usr/local/bundle
tmpfs:
- /var/www/html/tmp/pids
links:
- db
tty: true
stdin_open: true

volumes:
bundle:
driver: local
mysql:
driver: local
rails-sync-volume:
external: true

docker-sync.yml

1
2
3
4
5
6
7
8
9
10
version: '2'

syncs:
rails-sync-volume:
src: './'
sync_strategy: 'unison'
sync_excludes:
- '.docker-sync'
- '.git'
- '.gitignore'

新しいローカル開発環境の作成

既存設定ファイルのコピー

まずは新しいアプリケーションのルートディレクトリに、docker-compose.ymldocker-sync.ymlをコピーします。

1
2
$ cp app_A/docker-compose.yml app_B/docker-compose.yml
$ cp app_A/docker-sync.yml app_B/docker-sync.yml

docker-sync.ymlの編集

app_Bの方のdocker-sync.ymlを修正します。このままだと名前が被ってしまうので、名前を修正します。

1
2
3
4
5
6
7
8
9
10
version: '2'

syncs:
app_B-sync-volume:
src: './'
sync_strategy: 'unison'
sync_excludes:
- '.docker-sync'
- '.git'
- '.gitignore'

docker-syncの起動

編集したので起動してみます。

1
2
3
4
5
6
$ docker-sync start
ok Starting unison for sync app_B-sync-volume
ok Synced /Users/user/github/app_B/
success Unison server started
ok Synced /Users/user/github/app_B/
success Starting Docker-Sync in the background

コンテナを確認します。

1
2
3
$ docker ps
f98368a0a9e5 eugenmayer/unison:2.51.2.2 "/entrypoint.sh supe…" 28 hours ago Up About a minute 127.0.0.1:32771->5000/tcp app_B-sync-volume
129681851c64 eugenmayer/unison:2.51.2.2 "/entrypoint.sh supe…" 5 weeks ago Up 29 hours 127.0.0.1:32770->5000/tcp rails-sync-volume

無事起動できています。

docker-composeを試しに起動してみる

次はdocker-composeです。コピーした設定のままで一旦起動してみます。

1
2
3
4
$ docker-compose up
(省略)
Bind for 0.0.0.0:3306 failed: port is already allocated
(省略)

3306ポートは既存の開発環境が利用しているということで、エラーになってしまいました。
ポートの調整が必要ですね。

dockerのポート番号の調整を行う

ホスト側に公開するポートを3307に変更します

1
2
3
4
5
6
db:
image: mysql:5.6
environment:
MYSQL_ROOT_PASSWORD: password
ports:
- '3307:3306'

これで再度試します。

1
2
3
4
$ docker-compose up
(省略)
Bind for 0.0.0.0:3000 failed: port is already allocated
(省略)

ある意味予想通りですが、Railsのポートも重複しているので、3001に変更します

1
2
3
4
5
6
7
web:
build:
context: .
dockerfile: ./docker/rails/Dockerfile
command: ['rails', 's', '-p', '3000', '-b', '0.0.0.0']
ports:
- "3001:3000"

これでもう一度試します。

1
2
3
4
5
$ docker-compose up
(省略)
caecf6ab0866_app_B_web_1 | * Environment: development
caecf6ab0866_app_B_web_1 | * Listening on tcp://0.0.0.0:3000
caecf6ab0866_app_B_web_1 | Use Ctrl-C to stop

無事起動しました!

Rails側の調整

MySQLのポート番号を修正しているので、config/database.ymlport: 3307を追記してコンテナを再起動します。

動作確認

http://localhost:3001 にアクセスします。すると、エラーが表示されました…

Can't connect to MySQL server on 'db' (111)

dbへの接続ができていないようです…

コンテナ間通信時のポート指定

原因調査のためドキュメントを調べていると、以下のページが見つかりました。

https://matsuand.github.io/docs.docker.jp.onthefly/compose/networking/

ネットワークにより接続されているサービス間の通信は CONTAINER_PORT を利用します。

ということは、docker-compose.ymlでのポート指定が3307:3306なので、Railsアプリケーションのコンテナからは3306でアクセスすればよいということですね。config/database.ymlの修正は必要がなかったということです。

config/database.ymlport: 3307を削除したら、無事動作確認できました。

まとめ

今回新しくRailsアプリケーション開発環境を作成しました。既存の設定ファイルをコピーして少し編集しただけですが、docker-sync, docker-composeの理解が進んだと思います。

次回は新しいhexoのアプリケーション設定を行いたいと思います。

rails consoleでpathヘルパーを利用する

背景

_pathヘルパーで疑問が発生しました。qというクエリパラメータがnilだった場合、?q=となるのか、クエリパラメータなしになるのかが気になりました。

わざわざローカルでrails serverをせず確認する方法はないのかな?と思ってググってみました。

rails consoleでpathヘルパーの実験

appというオブジェクトを利用すると呼び出せるよと書かれていたので、早速試してみます

1
2
3
4
5
6
7
$ rails c
[1] pry(main)> app.login_path
=> "/login"
[2] pry(main)> app.login_path q: nil
=> "/login"
[3] pry(main)> app.login_path q: 'abc'
=> "/login?q=abc"

ということで、クエリパラメータの値がnilの場合はクエリパラメータなしになるようでした。

最初に思った?q=になる場合は、q: ''でした。

appというオブジェクトはなにか?

appというオブジェクトはなにでしょうか?試してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[1] pry(main)> app.class
=> ActionDispatch::Integration::Session
[2] pry(main)> ActionDispatch::Integration::Session.ancestors
=> [ActionDispatch::Integration::Session,
ActionDispatch::Routing::UrlFor,
ActionDispatch::Routing::PolymorphicRoutes,
ActionController::ModelNaming,
ActionDispatch::TestProcess,
ActionDispatch::Integration::RequestHelpers,
ActionDispatch::Assertions,
ActionDispatch::Assertions::RoutingAssertions,
ActionDispatch::Assertions::ResponseAssertions,
Rails::Dom::Testing::Assertions,
Rails::Dom::Testing::Assertions::TagAssertions,
Rails::Dom::Testing::Assertions::SelectorAssertions,
Rails::Dom::Testing::Assertions::SelectorAssertions::CountDescribable,
Rails::Dom::Testing::Assertions::DomAssertions,
Minitest::Assertions,
Object,
PP::ObjectMixin,
Delayed::MessageSending,
V8::Conversion::Object,
ActiveSupport::Dependencies::Loadable,
JSON::Ext::Generator::GeneratorMethods::Object,
Kernel,
BasicObject]

ancestorsのところは、バージョンによって異なるかもしれません。

これだけみてもわからないので、他の利用方法も見てみます。

app.getでリクエストを送る

app.getにパスを引数で渡すと、実際にリクエストを送ることができます

1
2
3
4
5
6
7
[1] pry(main)> app.get '/login'
Started GET "/login" for 127.0.0.1 at 2020-12-02 23:24:26 +0900
ActiveRecord::SchemaMigration Load (2.0ms) SELECT `schema_migrations`.* FROM `schema_migrations`
Processing by Users::SessionsController#new as HTML
(省略)
Completed 200 OK in 7125ms (Views: 7095.9ms | ActiveRecord: 11.5ms)
=> 200

なるほど、便利ですね!

ドキュメントにあるように、requestresponsecontrollerなどのメソッドもあり、最後のリクエストのそれぞれが取得できるようです。

まとめ

Railsは長い間使ってきましたが、まだまだ知らないことがいっぱいありそうです。

これからもなにか見つけたら紹介していきたいと思います。

hexoのテーマを決定する

前回までのまとめ

前回、hexoアプリケーションのテーマを選んで良さそうなものを利用しようとしたらうまく動かなかったところで終わりました。

今回は各ディレクトリの_config.ymlについて調べていきたいと思います。

テーマごとの_config.ymlの違いについて

デフォルトのテーマであるlandscapeは、theme/landscapeにある_config.ymlでテーマに依存した設定を行っていました。アプリケーションとしてはそうあるべきだと思います。

見た目が気に入って利用しようとしたhexo-theme-aomoriはアプリケーション直下の_config.ymlに設定してくれと書いてありました。違いは一体なんでしょうか。

hexo config

configを出力するコマンドnpx hexo configを試してみましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
$ npx hexo config
INFO Validating config
{
title: 'Hexo',
subtitle: '',
description: '',
author: 'John Doe',
language: 'en',
timezone: '',
url: 'http://example.com',
root: '/',
permalink: ':year/:month/:day/:title/',
permalink_defaults: null,
pretty_urls: { trailing_index: true, trailing_html: true },
source_dir: 'source',
public_dir: 'public',
tag_dir: 'tags',
archive_dir: 'archives',
category_dir: 'categories',
code_dir: 'downloads/code',
i18n_dir: ':lang',
skip_render: null,
new_post_name: ':title.md',
default_layout: 'post',
titlecase: false,
external_link: { enable: true, field: 'site', exclude: '' },
filename_case: 0,
render_drafts: false,
post_asset_folder: false,
relative_link: false,
future: true,
highlight: {
enable: true,
auto_detect: false,
line_number: true,
tab_replace: '',
wrap: true,
hljs: false
},
prismjs: {
enable: false,
preprocess: true,
line_number: true,
tab_replace: ''
},
default_category: 'uncategorized',
category_map: null,
tag_map: null,
date_format: 'YYYY-MM-DD',
time_format: 'HH:mm:ss',
updated_option: 'mtime',
per_page: 10,
pagination_dir: 'page',
theme: 'landscape',
server: {
port: 4000,
log: false,
ip: undefined,
compress: false,
header: true,
cache: false
},
deploy: { type: '' },
ignore: null,
meta_generator: true,
keywords: null,
index_generator: { per_page: 10, order_by: '-date', path: '' },
include: null,
exclude: null,
archive_generator: { per_page: 10, yearly: true, monthly: true, daily: false },
category_generator: { per_page: 10 },
tag_generator: { per_page: 10 },
marked: {
gfm: true,
pedantic: false,
breaks: true,
smartLists: true,
smartypants: true,
modifyAnchors: 0,
autolink: true,
mangle: true,
sanitizeUrl: false,
headerIds: true,
anchorAlias: false,
lazyload: false,
prependRoot: false,
postAsset: false,
external_link: { enable: false, exclude: [], nofollow: false }
}
}

表示された内容は、アプリケーション直下の_config.ymlの内容ですね。

しかし、ドキュメントには以下のように説明がありました。

_config.yml

1
2
3
4
5
theme: "my-theme"
theme_config:
bio: "My awesome bio"
foo:
bar: 'a'

themes/my-theme/_config.yml

1
2
3
4
bio: "Some generic bio"
logo: "a-cool-image.png"
foo:
baz: 'b'

Resulting in theme configuration:

1
2
3
4
5
6
7
8
{
bio: "My awesome bio",
logo: "a-cool-image.png",
foo: {
bar: "a",
baz: "b"
}
}

本来は各ディレクトリの設定内容が反映される、しかし、重複している場合はアプリケーショントップの_config.ymlを優先する、ということだと思います。

他のテーマは確かにそうなっていました。たまたま見た目が気に入ったテーマが他と動きが異なるだけのようです。

原因ははっきりわかりませんが、一旦ここで終わりにしたいと思います。自分でテーマを作るときにでもまた調査したいと思います。

利用するテーマを決定する

aomoriを諦めて、他のテーマを眺めていると、やはりTOPページがカードタイプのテーマがいいなと思いました。

するとなかなか良さそうなテーマが見つかりました。PHANTOMという名前です。

デモ https://www.codeblocq.com/assets/projects/hexo-theme-phantom/

github https://github.com/klugjo/hexo-theme-phantom

phantomのインストール

早速インストールします。READMEに書いてある通り実施します。

1
2
3
4
5
6
7
$ cd themes
$ git clone https://github.com/klugjo/hexo-theme-phantom.git
Cloning into 'hexo-theme-phantom'...
remote: Enumerating objects: 166, done.
remote: Total 166 (delta 0), reused 0 (delta 0), pack-reused 166
Receiving objects: 100% (166/166), 596.45 KiB | 753.00 KiB/s, done.
Resolving deltas: 100% (60/60), done.

次に必要なnpmパッケージをインストールします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ npm install --save hexo-renderer-scss
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated har-validator@5.1.5: this library is no longer supported

> node-sass@4.14.1 install /Users/user/github/business-book-reviews/business_blog/node_modules/node-sass
> node scripts/install.js

Downloading binary from https://github.com/sass/node-sass/releases/download/v4.14.1/darwin-x64-83_binding.node
Download complete ⸩ ⠋ :
Binary saved to /Users/user/github/business-book-reviews/business_blog/node_modules/node-sass/vendor/darwin-x64-83/binding.node
Caching binary to /Users/user/.npm/node-sass/4.14.1/darwin-x64-83_binding.node

> node-sass@4.14.1 postinstall /Users/user/github/business-book-reviews/business_blog/node_modules/node-sass
> node scripts/build.js

Binary found at /Users/user/github/business-book-reviews/business_blog/node_modules/node-sass/vendor/darwin-x64-83/binding.node
Testing binary
Binary is fine
+ hexo-renderer-scss@1.2.0
added 353 packages from 201 contributors and audited 544 packages in 28.474s

18 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities

動作確認

起動してみます

1
2
3
4
$ npx hexo server
INFO Validating config
INFO Start processing
INFO Hexo is running at http://localhost:4000 . Press Ctrl+C to stop.

ブラウザでlocalhost:4000にアクセスします。

hexoのテーマ phantom

なかなかいいのではないでしょうか。一旦このテーマで新しいアプリケーションを作っていきます。

まとめ

新しいアプリケーションのテーマを決めることができました。_config.ymlの謎が解けずに悔しいですが、またの機会にチャレンジしたいと思います。

次回は、同じサーバー上で2つのhexoアプリケーションを動作させるための設定を行っていきたいと思います。