Railsアプリケーションの作成

動機

RailsアプリケーションでGitHub Actionsを試そうと思い、その準備のために空のRailsアプリケーションを作成する必要がありました。その時のメモです。

Dockerを利用したほうがよいのでは?

publicなDocker imageを利用して誰でも作成できるようにしたほうがよいと思います。

こちらのコンテナを落としてみたんですが、Rubyのバージョンが2.3.3p222でした…他を探すにしても数が多すぎてよくわからなかったので、今回はローカル(Mac)に作成します。

Rubyのインストール

ローカルにanyenvをインストールしていました。最新バージョンのRubyをインストールするためにupdateします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ anyenv update
Updating 'anyenv'...
Updating 'anyenv/anyenv-update'...
Updating 'ndenv'...
Updating 'ndenv/ndenv-yarn-install'...
Updating 'ndenv/node-build'...
Updating 'rbenv'...
| From https://github.com/rbenv/rbenv
| c2cfbd1..c879cb0 master -> origin/master
Updating 'rbenv/ruby-build'...
| From https://github.com/rbenv/ruby-build
| 637ddf3..69ccbf4 master -> origin/master
| * [new tag] v20200518 -> v20200518
| * [new tag] v20200519 -> v20200519
| * [new tag] v20200520 -> v20200520

anyenvのupdateについてはこちら

インストールできるRubyを確認します

1
2
3
4
5
6
7
8
9
10
11
12
$ rbenv install -l
2.5.8
2.6.6
2.7.1
jruby-9.2.12.0
maglev-1.0.0
mruby-2.1.1
rbx-5.0
truffleruby-20.1.0

Only latest stable releases for each Ruby implementation are shown.
Use 'rbenv install --list-all' to show all local versions.

メッセージにあるようにstableの最新しか表示されないようになったようです。

最新の2.7.1をインストールします。

1
2
3
4
5
6
7
8
9
10
11
$ rbenv install 2.7.1
Downloading openssl-1.1.1g.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/ddb04774f1e32f0c49751e21b67216ac87852ceb056b75209af2443400636d46
Installing openssl-1.1.1g...
Installed openssl-1.1.1g to /Users/username/.anyenv/envs/rbenv/versions/2.7.1

Downloading ruby-2.7.1.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.1.tar.bz2
Installing ruby-2.7.1...
ruby-build: using readline from homebrew
Installed ruby-2.7.1 to /Users/username/.anyenv/envs/rbenv/versions/2.7.1

2.7.1を利用するようにします。

1
2
3
$ rbenv local 2.7.1
$ ruby -v
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin19]

Railsアプリケーションの作成

Gemfileの作成

bundler initします

1
2
$ bundler init
Writing new Gemfile to ~/github/github_actions_for_rails/Gemfile

Gemfileの更新

作成したGemfileを更新します。MySQLを利用する予定なのでmysql2を追加します。

1
2
gem 'rails'
gem 'mysql2'

bundle install

bundle installします

1
2
3
4
5
6
7
8
9
10
11
Installing mysql2 0.5.3 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

(中略)

linking shared-object mysql2/mysql2.bundle
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1

make failed, exit code 2

いつものようにエラーが出ます。毎回忘れるのですが、bundle configします。

1
$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib"

再度bundle installして完了です。

Railsアプリケーションの作成

rails newします。

1
$ bundle exec rails new . -d mysql

conflictするファイル(.gitignore, Gemfileなど)はoverwriteするようにします。不要なgemなどはスキップをするようにしたほうがよいと思いますが、今回はGitHub Actionsを試すためなので特に指定していません。GitHub ActionsのワークフローでMySQLと接続する処理を行おうと思うのでデータベースにmysqlを指定しています。

webpacker:installでメッセージが…

1
2
3
rails  webpacker:install
Webpacker requires Node.js >= 8.16.0 and you are using 8.9.4
Please upgrade Node.js https://nodejs.org/en/download/

ということで最新版にします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ndenv install v12.18.2
Downloading node-v12.18.2-darwin-x64.tar.gz...
-> https://nodejs.org/dist/v12.18.2/node-v12.18.2-darwin-x64.tar.gz
Installing node-v12.18.2-darwin-x64...
Installed node-v12.18.2-darwin-x64 to /Users/username/.anyenv/envs/ndenv/versions/v12.18.2

Installing Yarn...
/Users/username/.anyenv/envs/ndenv/versions/v12.18.2/bin/yarn -> /Users/username/.anyenv/envs/ndenv/versions/v12.18.2/lib/node_modules/yarn/bin/yarn.js
/Users/username/.anyenv/envs/ndenv/versions/v12.18.2/bin/yarnpkg -> /Users/username/.anyenv/envs/ndenv/versions/v12.18.2/lib/node_modules/yarn/bin/yarn.js
+ yarn@1.22.4
added 1 package in 0.82s
Installed Yarn 1.22.4
$ ndenv local v12.18.2
$ node -v
v12.18.2

再度webpacker:installを実行します。

1
2
3
4
5
6
7
8
$ rails webpacker:install
create config/webpacker.yml
Copying webpack core config
create config/webpack

(中略)

Webpacker successfully installed 🎉 🍰

最後に動作確認します

1
2
3
4
5
6
7
8
9
10
$ ./bin/rails s -b 0.0.0.0
=> Booting Puma
=> Rails 6.0.3.2 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.5 (ruby 2.7.1-p83), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

TOPページにアクセスするとデータベースがないというエラーが発生。config/database.ymlに書かれているデータベースを作成して無事TOPページを表示することができました。

まとめと次回

今回テスト用のRailsアプリケーションを作成しました。次回はこれを用いてGitHub Actionsでワークフローを作成したいと思います。

GitHub Actionsを試す

動機

GitHub Actionsという名前は知っていましたが、今まで使うことはありませんでした。今行っているプロジェクトでオンプレミス環境からパブリッククラウド(AWS)への移行を検討しています。

AWSではコンテナで環境を作成しようと思っており、ECSを利用する予定です。今日のDevelopers.IO2020のWebinarで、GitHub Actionsはすばらしいと説明されていたので使ってみようと思いました。

GitHubのCI/CDツール GitHub Actions

今までもCircleCIなどのツールは存在していました(わたしもCircleCIは使ったことがあります)。ソースコード管理はGitHubなので、GitHubと連携する必要がありました。しかしGitHub Actionsなら連携の設定をすることなくそのまま利用可能です。便利ですね。

試してみる

一度テスト用のリポジトリでGitHub Actionsを試してみます。

リポジトリの作成

適当にリポジトリを作成します。実際のリポジトリを想定してプライベートリポジトリを作成します。

GitHub Actionsの作成

GitHubのリポジトリを選択した状態で、上のActionsというタブをクリックします。すると以下のような表示になります。

GitHub Actions作成
今回はテストで作成するので、Set up this workflowをクリックします。

workflowの内容は、リポジトリ内のコードによって変わってきます。Dockerfileが保存されているリポジトリでActionsタブをクリックすると、docker-composeコマンドを実行するworkflowになりました。今回は空のリポジトリなので、echoするだけの単純なworkflowになったのだと思います。

workflowの保存

Set up this workflowをクリックすると、設定ファイルを編集する画面に遷移します。
workflowの編集

ファイル名を適当に変更します。workflow内はそのままで、右上のStart commitをクリックします。workflowのyamlは.github/workflows/ディレクトリの下に作成します。

workflowの動作確認

どういったトリガーでworkflowが実行されるかはyamlのonを確認します。今回のyamlは

1
2
3
4
5
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

となっており、masterブランチのpushと、pull_requestになります。つまり、

  • masterブランチを更新
  • masterブランチへのPull Requestを作成

で実行されると言うことになります。試しにmasterブランチへのPullRequestを作成すると以下のようになります。

PullRequestの作成

workflowが実行された結果が表示されています。workflowの結果を確認するにはActionsタブをクリックします。左のペインですべてのworkflow(All workflows)か、特定のworkflowを選択すると、選択したものの結果が表示されます。

まとめ

今回GitHub actionsの初めの1歩として簡単に触ってみました。比較的わかりやすくスムーズに進みました。

次回はもう少しプロダクションよりな感じにしたいと思います。Ruby(Rails)でのテストの実装や、データベースなどのコンテナの立ち上げ方を理解し、workflowを試したいと思います。

Dockerのlink機能 その2 アプリケーションサーバの設定

前回までのまとめ

前回Docker実践ガイドの第5章のlink機能を用いたアプリケーションサーバ用コンテナとDB用コンテナを起動するところまで行いました。

今回はアプリケーションサーバの設定を行い、linkしているコンテナが正しく動いているかを確認します。

アプリケーションサーバの設定

まずはじめにアプリケーションサーバのIPアドレスを確認します

1
2
$ docker container inspect mw0001 --format={{'.NetworkSettings.IPAddress'}}
172.17.0.4

docker container inspect コンテナ名とすることでコンテナ名に指定したコンテナの情報をJSON形式で取得することができます

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
$ docker container inspect mw0001
[
{
"Id": "a4d1693a7981100b015d75f958468069c7418e11dad13e042bf26f5454246581",
"Created": "2020-06-22T11:03:55.9929367Z",
"Path": "/sbin/init",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
...
(中略)
...
"MacAddress": "02:42:ac:11:00:04",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "d6a62553445bbae6f040e8972635b12995ca2b23f0328ab8736f7a2b71d541bb",
"EndpointID": "73fae0f570728362bbe4c504ef79e98df191c9352375e9d1805b73ef56613ad2",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.4",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:04",
"DriverOpts": null
}
}
}
}
]

--formatオプションで取得する値を指定することができます。

ブラウザからのアクセス

アプリケーションサーバのIPアドレスがわかったので、ブラウザからアクセスしてみます。
URLはhttp://172.17.0.4/wiki/です。

しかし応答が返ってきません。-pオプションを利用していないのにIPアドレスがわかったところで通信できるのでしょうか?書籍ではLinuxをホストOSとして動作させているので可能なのかもしれません。わたしの手元の環境はDocker Desktopだからかもしれません。

一旦-pオプションを利用してアクセスできることを確認します。

1
2
3
$ docker stop mw0001
$ docker rm mw0001
$ docker container run -itd --tmpfs /tmp --tmpfs /run --mount type=bind,src=/sys/fs/cgroup,dst=/sys/fs/cgroup,readonly --stop-signal SIGRTMIN+3 -v /var/www/html --name mw0001 -h mw0001 --link mariadb0001:db0001 -p 8080:80 centos:mw0001 /sbin/init

今回は-p 8080:80を設定しています。よって、URLはhttp://localhost:8080/wiki/です。すると、正しく表示されました。

が、しかし、データベースの接続設定のところで、mariadb0001のIPアドレス、ユーザー名、パスワードを入力すると

1
2
Cannot access the database: MySQL server has gone away (172.17.0.3)。
以下のホスト名、ユーザー名、パスワードを確認してから再度試してください。

というエラーが出てしまった。パスワードは間違ってなさそうです。

ちょっと原因がわからないので、一旦linkオプションを用いたコンテナ間通信はここまでにしようと思います。

次回はコンテナ間通信を行うもう一つの方法、bridgeネットワークを用いたコンテナ間通信を試したいと思います。

Dockerのlink機能

前回までのまとめ

前回Docker実践ガイドの第5章のlinkオプションの説明を読んで環境構築をしようとしたところ、Dockerfileの記述方法をミスってしまい、環境構築まで到達できませんでした。

なんとかDB用コンテナが起動できたところまで進んだので、アプリケーションサーバ用コンテナの準備をしたいと思います。

DB用コンテナの確認

起動の確認

DB用コンテナが起動していることを確認します

1
2
3
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
65b5af36d7ac centos:mariadb0001 "/mariadb.sh" 4 days ago Up 4 days 3306/tcp mariadb0001

起動オプションの確認

コンテナ起動オプションを確認します。

1
$ docker run -d -e DBNAME=testdb -e MARIADBUSER=root -e MARIADBPASSWORD=mysqlPassword -v /var/lib/mysql:/var/lib/mysql:rw --name mariadb0001 centos:mariadb0001

マウントしているディレクトリの確認

-vオプションで指定しているディレクトリを確認します。

1
2
3
4
5
6
$ ls /var/lib/mysql
65b5af36d7ac.pid ib_logfile1 mysql.sock
aria_log.00000001 ibdata1 performance_schema
aria_log_control ibtmp1 test
ib_buffer_pool multi-master.info
ib_logfile0 mysql

データファイルやログファイルなどが保存されています。コンテナが/var/lib/mysqlとして利用しているのがわかります。

アプリケーションサーバ用コンテナのビルド

DB用コンテナが起動していることがわかったので、次にアプリケーションサーバ用コンテナを準備します。
書籍に倣って、Mediawikiをインストールします。

Dockerfileの作成

Dockerfileは以下のようになっています。

1
2
3
4
5
6
7
8
9
FROM centos:7.5.1804
RUN yum remove -y php* && \
yum install -y http://rpms.famillecollet.com/enterprise/remi-release-7.rpm && \
yum install -y --enablerepo=remi,remi-php72 php php-mysqlnd php-mbstring php-xml httpd openssh-clients && \
curl -O https://releases.wikimedia.org/mediawiki/1.31/mediawiki-1.31.0.tar.gz && \
tar xzvf /mediawiki-1.31.0.tar.gz -C /var/www/html/

WORKDIR /var/www/html
RUN ln -sf ./mediawiki-1.31.0 wiki && systemctl enable httpd

WORKDIRについてしっかり理解していなかったので、ドキュメントを参照しました。

ざっくりの理解だと、WORKDIRを宣言した後はそのディレクトリでRUN, CMD, ENTRYPOINT, COPY, ADDが実行されるということです。その名の通りworking directoryを指定しています。WORKDIRに関するベストプラクティスも記載されていました。コマンドを実行するたびにどこかのディレクトリに移動するのではなくWORKDIRからのパスで処理を実行しましょうということです。

イメージのビルド

Dockerfileの作成が終わったのでイメージをビルドします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ docker image build -f Dockerfile -t centos:mw0001 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM centos:7.5.1804
---> cf49811e3cdb
Step 2/4 : RUN yum remove -y php* && yum install -y http://rpms.famillecollet.com/enterprise/remi-release-7.rpm && yum install -y --enablerepo=remi,remi-php72 php php-mysqlnd php-mbstring php-xml httpd openssh-clients && curl -O https://releases.wikimedia.org/mediawiki/1.31/mediawiki-1.31.0.tar.gz && tar xzvf /mediawiki-1.31.0.tar.gz -C /var/www/html/
---> Running in c808fb96ff1d

(中略)

Removing intermediate container c808fb96ff1d
---> 90815ed52b36
Step 3/4 : WORKDIR /var/www/html
---> Running in 5b4fdf6b5b97
Removing intermediate container 5b4fdf6b5b97
---> e535a272bbf0
Step 4/4 : RUN ln -sf ./mediawiki-1.31.0 wiki && systemctl enable httpd
---> Running in 9733db996f6f
Created symlink /etc/systemd/system/multi-user.target.wants/httpd.service, pointing to /usr/lib/systemd/system/httpd.service.
Removing intermediate container 9733db996f6f
---> 67b7fb3ff7d6
Successfully built 67b7fb3ff7d6
Successfully tagged centos:mw0001

無事イメージのビルドができました。

tarコマンドにvオプションを指定してしまったため、Mediawikiのファイル一覧が出力されてログが流れてしまった。vオプションはなしにした方がよさそうです。

アプリケーションサーバ用コンテナの起動

アプリケーションサーバ用コンテナを起動します。

1
$ docker container run -itd --tmpfs /tmp --tmpfs /run --mount type=bind,src=/sys/fs/cgroup,dst=/sys/fs/cgroup,readonly --stop-signal SIGRTMIN+3 -v /var/www/html --name mw0001 -h mw0001 --link mariadb0001:db0001 centos:mw0001 /sbin/init

オプションがたくさん指定されていますので、一つずつ見ていきます。

  • -i
    標準入力を利用します
  • -t
    ttyを利用します。こちらに書かれているように、シェルなどのインタラクティブなプロセスではttyを割り当てるために-itオプションを指定します。今回の場合は-dも指定しているため、-itdとなっています。
  • –tmpfs
    tmpfsを割り当てます。
  • –mount
    srcはホストOS上のディレクトリを指定するはずですが、今利用しているMacには/sys/fs/cgroupはありませんでした。後ほど詳しく調べます。
  • –stop-signal
    こちらの内容だと、コンテナ停止のためのシグナルを指定するオプションのようです。
  • -h
    コンテナのホスト名を指定します。
  • –link
    前回にも記載しましたが、コンテナを指定し、コロンの後のエイリアス名でをprefixにして環境変数にアクセスできるようになります。

アプリケーションサーバ用コンテナとDB用コンテナの連携確認

アプリケーションサーバ用コンテナの起動確認

起動していることを確認します

1
2
3
4
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a4d1693a7981 centos:mw0001 "/sbin/init" 3 hours ago Up 3 hours mw0001
65b5af36d7ac centos:mariadb0001 "/mariadb.sh" 4 days ago Up 4 days 3306/tcp mariadb0001

環境変数の確認

環境変数を確認します

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ docker container exec -it mw0001 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=mw0001
TERM=xterm
DB0001_PORT=tcp://172.17.0.3:3306
DB0001_PORT_3306_TCP=tcp://172.17.0.3:3306
DB0001_PORT_3306_TCP_ADDR=172.17.0.3
DB0001_PORT_3306_TCP_PORT=3306
DB0001_PORT_3306_TCP_PROTO=tcp
DB0001_NAME=/mw0001/db0001
DB0001_ENV_MARIADBUSER=root
DB0001_ENV_MARIADBPASSWORD=mysqlPassword
DB0001_ENV_DBNAME=testdb
HOME=/root

エイリアス名の大文字をprefixとした環境変数が取得できることがわかります。これらの環境変数を使ってDB用コンテナにアクセスすることができます。

hostsファイルの確認

次にhostsファイルを確認します

1
2
3
4
5
6
7
8
9
$ docker container exec -it mw0001 cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3 db0001 65b5af36d7ac mariadb0001
172.17.0.4 mw0001

DB用コンテナがhostsファイルに登録されているのがわかります。

まとめ

link機能を使うと

  • 他のコンテナの環境変数にアクセスできるようになる
  • 他のコンテナが/etc/hostsファイルに記載される

link機能は単体のホストOS上で複数のコンテナが連携する場合に有用です。Dockerにおける複数コンテナの連携の基本となっているのでしっかり理解したいです。

SEO対策 URLから日付を削除する

URLに日付は含めない方がいい?

SEOに関する記事を読みました。タイトルは3,000万ユーザーを集客した結果わかった、SEOに関する30の教訓というとても興味深いものでした。ざっと30の教訓に目を通すと一つ気になるものがありました。

URLに日付は含めない

詳しい内容は以下です。

かつて、私は下記のようにURL内に日付を含めていた。
Neilpatel.com/2017/12/title-of-post/
こうすることで、そのコンテンツが特定の日付と関連していると、検索エンジンが想定してしまうことになる。その日付が古くなるにつれ、検索エンジンはあなたのコンテンツの関連性は低く、古いコンテンツであると想定してしまうだろう。
タイトルから日付を削除したタイミングで、SEOからのトラフィックは30日間で58%上昇した。

思いっきりURLに日付入ってました。hexoのデフォルトが/年/月/日/タイトルになっていました。

これは修正せねばということで、修正したいと思います。
修正はURLが少ない今のうちに早めにやっておいた方がよさそうです。

hexoのURL設定

hexoのURLの設定は_config.ymlにあります。

設定内容はこちらです。

1
2
3
4
5
# URL
url: https://book-reviews.blog
root: /
permalink: :year/:month/:day/:title/
permalink_defaults:

このpermalinkから日付を削除します。

1
permalink: :title/

タイトルだけが良いかどうかわかりませんが、今後変更がないように余計なものをURLに含ませないようにします。

nginxのリダイレクト設定

古いURLにアクセスがいく可能性があるので、古いURLから新しいURLにリダイレクトさせる必要があります。nginxにリダイレクトの設定をします。

serverディレクティブに1行追加します。

1
rewrite ^/\d{4}/\d{2}/\d{2}/(.+)/$ /$1/ permanent;

syntaxのチェックをします。

1
2
$ sudo nginx -t
nginx: [emerg] directive "rewrite" is not terminated by ";" in /etc/nginx/conf.d/hexo.conf:10

セミコロンで終わってないというメッセージのようですが、セミコロンで終わっているつもりなのですが…

試しに繰り返し回数の{4}+に変更してみるとsyntax OKが出ます。理由を調査していると{}を利用するときはダブルクォートで括る必要があるようです。

設定を修正します。

1
rewrite "^/\d{4}/\d{2}/\d{2}/(.+)/$" /$1/ permanent;

再度syntaxのチェックを行います。

1
2
3
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

大丈夫そうです!

サービスの再起動

サービスを再起動して設定を反映します。
今回の場合、特に起動順序などはありません。ほぼ同時にhexoとnginxの再起動が必要です。

hexoの再起動

hexoは以前systemdで起動できるようにしました。(その時の記事はこちら

なので、systemdを用いて停止起動を行います。

1
2
$ sudo systemctl stop hexo
$ sudo systemctl start hexo

nginxの設定再読み込み

syntaxのチェックは終わっているので、修正した設定を読み込みます。

1
2
$ sudo service nginx reload
Redirecting to /bin/systemctl reload nginx.service

nginxもsystemdで設定を再読み込みしました。

動作確認をするとちゃんとリダイレクトされています!
URLから日付を削除することでSEOにどんな影響があるのかをみていきたいと思います。

TypeScriptことはじめ

TypeScriptを学びたい

わたしはサーバーサイドエンジニアとして長らく働いてきました。その間何度かJavaScriptの習得にチャレンジしましたが、今のところ手にした感はありません…

そんな折、TypeScriptの話をよく聞くようになりました。TypeScriptは強い型付けが可能なJavaScriptという感じでしょうか。強い型付けが好きなわたしとしては、JavaScriptの習得にはこの言語しかないと思わせるものがあります。

また、この前のDevelopers.IO 2020でもAWS CDKでTypeScriptを利用した例が紹介されていました。今後もAltJSとして利用されることが多くなると予想されます。TypeScriptを学び始めるのにはちょうどよい時期なのでしょう。

今回はWEB+DB PRESS Vol.117の特集で取り上げられていた内容をもとに学んでいこうと思います。

TypeScript環境構築

Node.jsのインストール

docker runでエラー 'No such file or directory'

さくらのDocker入門

Dockerの理解を深めるために、さくらのDocker入門を見ながら実際に手を動かしていきました。すると、早速理解できていないことに気づきました。

1
2
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:5.7
docker run --name some-wordpress -e WORDPRESS_DB_PASSWORD=my-secret-pw --link some-mysql:mysql -d -p 8080:80 wordpress

この部分です。docker run--linkオプションの意味を理解していなかったので、手元にあるDocker実践ガイド--linkオプションの説明を探しました。

linkオプションについて

mw0001というコンテナの起動で--link mariadb0001:db0001オプションを利用しており、その説明は以下のようになっていました。

  • これから起動するコンテナmw0001は、稼働中のコンテナmariadb0001とリンクを張る
  • コンテナmw0001において、mariadb0001コンテナの環境変数を取得することができる
  • db0001はエイリアス名である
  • mw0001が取得できるmariadb0001コンテナの環境変数名には、エイリアス名を大文字にした文字列が付与される

ということなので、エイリアス名を大文字にしたDB0001をprefixとした環境変数として、mariadb0001コンテナの環境変数を取得できるのだろうことはわかりました。

上記の説明の次のページになんのためにlinkオプションを利用するのかが記載されていました。

Column linkとは

Dockerにおけるlinkは、単一のDocker環境内で稼働する複数のコンテナ間において、コンテナの環境変数を他のコンテナで利用する機能です。
コンテナは、起動するたびにIPアドレスなどが変わるため、コンテナ間で通信を行う際に、環境変数に定義されたIPアドレスやポート番号を環境変数に定義し、その環境変数を別のコンテナで利用できるようにします。

なるほど、そういう目的なのですね。実際に試してみます。

コンテナmariadb0001の作成

まず、MariaDBのコンテナを作成します。本にしたがって行いますので、ベースイメージはcentos:7.5.1804となります。

yumリポジトリの追加

MariaDBの最新版を導入するため、リポジトリファイルを用意し、yum.mariadb.orgで提供されるリポジトリを使えるようにします。

1
2
3
4
5
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.4/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

書籍のbaseurlはアクセスできなかったので修正しました。

Dockerfileの作成

Dockerfileは書籍のものを記載(したつもりでした)。

1
2
3
4
5
6
7
8
9
FROM centos:7.5.1804
COPY MariaDB.repo /etc/yum.repos.d/MariaDB.repo
RUN yum update -y && yum install -y MariaDB-server MariaDB-client
COPY server.cnf /etc/my.cnf.d/
COPY mariadb.sh /mariadb.sh
RUN chmod +x /mariadb.sh
VOLUME ['/var/lib/mysql']
EXPOSE 3306
ENTRYPOINT ['/mariadb.sh']

Dockerfileに記載のENTRYPOINTのシェルを準備したり、MariaDBの設定ファイルの準備も行いました。

イメージのビルド

では早速imageをbuildします。Dockerfileがあるディレクトリに移動し、以下のコマンドを実行します。

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
$ docker image build -f Dockerfile -t centos:mariadb0001 .
Sending build context to Docker daemon 6.144kB
Step 1/9 : FROM centos:7.5.1804
---> cf49811e3cdb
Step 2/9 : COPY MariaDB.repo /etc/yum.repos.d/MariaDB.repo
---> a5c6607df1f1
Step 3/9 : RUN yum update -y && yum install -y MariaDB-server MariaDB-client
---> Running in 8b498184156d

(中略)

Complete!
Removing intermediate container 8b498184156d
---> 95b6c03d5190
Step 4/9 : COPY server.cnf /etc/my.cnf.d/
---> d6c1d2ece48a
Step 5/9 : COPY mariadb.sh /mariadb.sh
---> 16c33f1ca0ad
Step 6/9 : RUN chmod +x /mariadb.sh
---> Running in 292c931ba940
Removing intermediate container 292c931ba940
---> 42e1af28711d
Step 7/9 : VOLUME ["/var/lib/mysql"]
---> Running in 67b800c69838
Removing intermediate container 67b800c69838
---> 2a0b5d4f6f3d
Step 8/9 : EXPOSE 3306
---> Running in ec0143ebd8db
Removing intermediate container ec0143ebd8db
---> ee16c6003675
Step 9/9 : ENTRYPOINT ["/mariadb.sh"]
---> Running in 21382026dc44
Removing intermediate container 21382026dc44
---> 8b9c2c536dc1
Successfully built 8b9c2c536dc1
Successfully tagged centos:mariadb000

ということで、無事ビルドできました。

コンテナの起動

ビルドしたイメージからコンテナを起動します。

1
2
$ docker run -d -e DBNAME=testdb -e MARIADBUSER=root -e MARIADBPASSWORD=mysqlPassword -v /var/lib/mysql:/var/lib/mysql:rw --name mariadb0001 centos:mariadb0001
1ad8c112f237ed46c442f90373ff11d126753b930703f81caa6b3b8f6b6c2620

起動したコンテナのIDが返ってきました。

無事起動できていると思いきや

1
2
3
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1ad8c112f237 centos:mariadb0001 "/bin/sh -c ['/maria…" 3 seconds ago Exited (127) 3 seconds ago mariadb0001

Exited…止まってしまっています…

起動しない原因の調査

まずはログをみてみます。

1
2
$ docker logs 1ad8c112f237
/bin/sh: [/mariadb.sh]: No such file or directory

DockerfileでCOPYしているはずのファイルがないと言われています。そんなはずはないと思って、なんどか再ビルドしたりしてみましたが全く変化がありません。

検索して出てくる結果は、docker image buildのタイミングでNo such file or directoryエラーが出るというものばかりで、docker container runのタイミングでエラーになるケースは見当たりません。

エラーの原因

もう一度Dockerfileをよくみてみました。すると、書籍はファイルパスがダブルクォートで括られていましたが、自分のDockerfileはシングルクォートになっていました。

もしやと思ってダブルクォートに変更後、再ビルド→起動してみると、無事起動しました!

1
2
3
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
65b5af36d7ac centos:mariadb0001 "/mariadb.sh" 20 seconds ago Up 19 seconds 3306/tcp mariadb0001

ダブルクォートで記載しないといけない点に関しては公式ドキュメントにも記載がありました。
こちらの下の方に注釈があり、その中で記載されています。

1
2
exec 形式は JSON 配列として解釈されます。
したがって文字列をくくるのはダブルクォート(")であり、シングルクォート(')は用いてはなりません。

なるほど、JSONなのですね、JSONでは文字列を括るのはダブルクォートでないとエラーになってしまいます。そういうことなのですね。

まとめ

Dockerfileでのexec形式([]で引数を取るパターン)では、文字列をダブルクォートで括るということを忘れないようにします。

次回はちゃんとlinkのテストを行いたいと思います。

その他

この調査でよく使ったコマンド

  • docker container prune
    止まっているコンテナを一括削除

  • docker ps -a
    全てのコンテナの一覧表示

  • docker logs <コンテナID>
    コンテナのログを出力する。エラーが起こった時には真っ先に確認する

  • docker image rm <イメージ名>
    指定したイメージを削除する

単数形/複数形の変換がおかしいときの外部キー制約がエラーになる場合の対処法

外部キー制約の設定ができない

テーブルを作成するためにmigrationファイルを作成しdb:migrateしたら思いもよらないエラーが発生したので対処法を調査しました。

エラー内容

既存のテーブル(document_bases)があり、そのテーブルのidを外部キーに持つテーブル(document_views)を作成します。

migrationファイルを準備します

1
2
3
4
5
6
7
8
class CreateDocumentView < ActiveRecord::Migration
def change
create_table :document_views do |t|
t.references :document_base, index: true, foreign_key: { name: 'document_base_id_on_document_views' }
t.timestamps
end
end
end

db:migrateします。すると以下のようなエラーが表示されました。

1
ActiveRecord::StatementInvalid: Mysql2::Error: Key column 'document_basis_id' doesn't exist in table: ALTER TABLE `document_views` ADD CONSTRAINT `document_base_id_on_document_views` FOREIGN KEY (`document_basis_id`) REFERENCES `document_bases` (`id`)

原因調査

最初なぜエラーになるのか全くわからなかったのですが、よく見るとKey column 'document_basis_id'となっていて、存在するカラムdocument_base_idと異なっています。

調べてみるとbasesはbaseの複数形でもあり、basisの複数形でもあるようです。basesから単数形に戻すときにbaseではなくbasisを選択されてしまっているようです。

対応方法

t.referencesのオプションであるforeign_keyでなんとか指定できないかと思い、add_foreign_keyのオプションを調べてみました。

https://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/add_foreign_key

するとoptionsの説明の最初に答えが書いてありました。

1
2
:column
The foreign key column name on from_table. Defaults to to_table.singularize + "_id"

Defaults to to_table.singularize + "_id"と書かれているので、singularizeメソッドを試してみました。

1
2
[1] pry(main)> 'bases'.singularize
=> "basis"

ということでforeign_keyのオプションにcolumn: :document_base_idを指定することで無事migrateできました。

修正済migrationファイル

1
2
3
4
5
6
7
8
class CreateDocumentView < ActiveRecord::Migration
def change
create_table :document_views do |t|
t.references :document_base, index: true, foreign_key: { name: 'document_base_id_on_document_views', column: :document_base_id }
t.timestamps
end
end
end

まとめ

Railsの単数形/複数形変換で存在しないカラム名/テーブル名に変換されてしまった場合は明示的に名前を指定するようにする。

APIドキュメントをちゃんと読むようにする。

AmazonLinux2のDocker Imageにnginxをインストールする

ベースイメージの選択

最終目標として、AWS ECS(以下ECS)でRailsアプリケーションを動作させたいと思っています。ECSで利用するイメージのベースはなにがよいかわかっていないので、とりあえずAmazonLinux2を利用します。一旦テストのためにローカルでDockerfileを作成してみます。

AmazonLinux2のイメージはdockerhubにありました。
https://hub.docker.com/_/amazonlinux

tagはlatest2とすれば大丈夫のようです。

nginxのインストール

Dockerfileを記述していきます

1
2
3
FROM amazonlinux:latest
RUN yum update -y
RUN yum install -y nginx

docker image buildを実行します

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ docker image build -f Dockerfile -t al2_nginx:latest .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM amazonlinux:latest
---> 7f335821efb5
Step 2/3 : RUN yum update -y
---> Running in 1d974faaaefa
Loaded plugins: ovl, priorities
Resolving Dependencies
--> Running transaction check
---> Package glib2.x86_64 0:2.56.1-4.amzn2 will be updated
(中略)
FROM amazonlinux:latest

Complete!
Removing intermediate container 1d974faaaefa
---> b36e0bbfc4cf
Step 3/3 : RUN yum install -y nginx
---> Running in f244f466cb1b
Loaded plugins: ovl, priorities
No package nginx available.
Error: Nothing to do
The command '/bin/sh -c yum install -y nginx' returned a non-zero code: 1

nginxのパッケージがないようなので調べてみると、 amazon-linux-extras install nginx1.12でインストールが可能とのこと。Dockerfileを編集します。

1
2
3
FROM amazonlinux:latest
RUN yum update -y
RUN amazon-linux-extras install nginx1.12

そして、再度ビルドします

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ docker image build -f Dockerfile -t al2_nginx:latest .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM amazonlinux:latest
---> 7f335821efb5
Step 2/3 : RUN yum update -y
---> Using cache
---> b36e0bbfc4cf
Step 3/3 : RUN amazon-linux-extras install nginx1.12
---> Running in de67234984e9
NOTE: The livepatch extra is in public preview, not meant for production use

Topic nginx1.12 has end-of-support date of 2019-09-20
Loaded plugins: ovl, priorities
Cleaning repos: amzn2-core amzn2extra-nginx1.12
(中略)
* Extra topic has reached end of support.
Removing intermediate container de67234984e9
---> 97bc3952d1a8
Successfully built 97bc3952d1a8
Successfully tagged al2_nginx:latest

依存関係によって82ものパッケージがインストールされますが、無事nginxはインストールできたようです。

ただ気になるメッセージがありました。

1
2
3
NOTE: The livepatch extra is in public preview, not meant for production use

Topic nginx1.12 has end-of-support date of 2019-09-20

livepatch extraというのは本番用ではないということと、nginx1.12のサポートがすでにを終わっているということでした。

nginxのバージョン変更

nginxのバージョンをあげるにはどういう風に指定すればいいのかを調べてみると、以下のページが見つかりました。

https://dev.classmethod.jp/articles/al2-nginx1-new-extras-topic/

記事に書いてある内容ですと、nginx1と指定するとよさそうです。
再度Dockerfileを更新します。

1
2
3
FROM amazonlinux:latest
RUN yum update -y
RUN amazon-linux-extras install nginx1

再度buildします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ docker image build -f Dockerfile -t al2_nginx:latest .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM amazonlinux:latest
---> 7f335821efb5
Step 2/3 : RUN yum update -y
---> Using cache
---> b36e0bbfc4cf
Step 3/3 : RUN amazon-linux-extras install nginx1
---> Running in a7280e4e4c82
NOTE: The livepatch extra is in public preview, not meant for production use

Loaded plugins: ovl, priorities
Cleaning repos: amzn2-core amzn2extra-nginx1
6 metadata files removed
2 sqlite files removed
0 metadata files removed
Loaded plugins: ovl, priorities
Resolving Dependencies
--> Running transaction check
---> Package nginx.x86_64 1:1.16.1-1.amzn2.0.1 will be installed

今度は1.16.1がインストールできました(2020年6月現在)

まとめ

  • AmazonLinux2のDockerImageはDockerhubから取得できる。
  • AmazonLinux2にnginxをインストールする場合は、amazon-linux-extrasコマンドを利用する
  • トピックをnginx1とすることで1系の最新バージョンがインストールできる
  • extrasリポジトリで配布されるパッケージは、トピック変更が発生するため、定期的なOSパッチ更新などのタイミングで利用中のトピックのサポート状況を確認する。Dockerfileのメンテナンスを行う。

次にRubyのインストールとRailsアプリケーションのデプロイを行いたいと思います。