巨人の足元でたじlog

そうして言葉を軽んじるから――― 君は私の言葉を聞き逃す

AWSで典型パターンのウェブサービスを1から構築してみる備忘録

2019/02/02 17:01 タイムトライアル始めます。

手を動かしながら2週間で学ぶ AWS 基本から応用まで | Udemy
これをベースにしつつ、自分なりに理解しながらやっていきます。

ドメイン取得

まずは、時間がかかるので最初にドメインを取っておきます。

ドメイン 無料」で検索。

https://my.freenom.com

これが良さそうでした。

適当なドメインを考えて、チェック。

空いているドメインだったので、選択してチェックアウトします。

Googleアカウントで登録。メールが飛んでいるので、認証します。

入力項目は、デフォルトの状態で一旦登録して、バリデーションかかったやつだけ入力します。

「アドレス 英語」

JuDress | 住所→Address変換

500回使ってるサイト

なんか登録がうまくいかないので、別のサービス使います。

無料で独自ドメインを取得できる!無料ドメインの取得サービス6選|ferret [フェレット]

に出てくる、Dot TK - 新しい無料ドメインを探しますを使います。

と思ったけど、フォーム画面じゃないのでギリセーフかもしれないけど、通常ページがhttps通信じゃないし、ドメイン選択したら、結局freenomのページにリダイレクトされた。糞。

しゃあなしで、無料ダイナミックDNS(DDNS)サービス - ieServer.Netをやる。

と思ったけど、無料ドメインと無料ダイナミックドメインはちょっと別物?

これは積んだーかと思ったところに、「これはブラウザの問題なのでは?」という僥倖。

safariから登録したら完了しましたー!

取得ができましたー!

取得したドメインにアクセスしても何も起きません。

このサイトにアクセスできません hogehoge.tk のサーバーの IP アドレスが見つかりませんでした。 となる。

そりゃそう。

Route53

Route53 > Dashboard > Hosted zone > Create Hosted Zone

Domain Name: freenomで取得したドメイン
Comment: 
Type: Public Hosted Zone

NSのvalueに4つのdnsがあるので、これを控えておきます。

freenomのページのドメインのManagementページに移ります。

DNSサーバー名を入力する場所があるので、4つのDNSを入力して保存します。

→まだ反映されていないです。
反映には数時間から数日かかるらしいです。

なので、一旦待ちます。ここまで約50分! くっそー登録に無駄に不要な時間かかってしまったためだ。

正味この作業は10分で終わるはずだった。。。

VPCをつくる。

最終的に目指す構成としては、一番ベタな構成を作っていきたい。 すなわち以下のような構成。

と、パワーポイントで作ろうとしたけど、プライベートMacパワポ入れてなかったので、Googleプレゼンテーションで作ることに。
と、 ちょっと様式が違って面倒くさかったので、紙でかく。これが一番早い。

f:id:benzenetarou:20190202180053j:plain

VPCをつくる

基本方針としては、デフォルトを使用する。
やむを得ず設定する場合は、ちゃんと意味を理解して設定することにします。

VPC > Create VPC

Name tag: hogehoge
IPv4 CIDR block: 10.0.0.0/16
IPv6 CIDR block: No IPv6 CIDR Block (default)
Tenancy: Default (後で調べる)

サブネットをつくる

今回は4つのサブネットを作りたいと思います。 CIDRブロックをどうしようか。
10.0.0.0/16が 65536 - 5(AWSの予約分) = 65531なので、
一旦はわかりやすさのために/24とかで切ります. 10.0.0.0 - 10.0.0.255ですね 作ったサブネットのサイダーたちは以下

10.0.0.0/24 hoge-a-public
10.0.1.0/24 hoge-a-private
10.0.2.0/24 hoge-c-public
10.0.3.0/24 hoge-c-private

この時点では、まだこの子たちはプライベートサブネットとか、パブリックサブネットとか、何も設定されていません。
サブネットは生まれながらにして、パブリック/プライベートと決まっているわけではないのです。
人は生まれた瞬間には善で、その後の人生で悪に染まっていくという性善説とは違いますね。
ほんとに余談ですが、性善説に意味については誤解して認識している人が多いです。性善説と性悪説のよくある誤解、他 いわゆる誤解されて使われている意味を表す言葉はないのでしょうかね?これは余談が過ぎた。

ルーティング

この作成したサブネットたちの運命を決めるのは、すべてルーティングなんですね。
f:id:benzenetarou:20190202182745p:plain

ルートテーブル↓
f:id:benzenetarou:20190202182740p:plain

これを見ると
送信先: 10.0.0.0/16
ターゲット: local
とある。 つまり10.0.0.0/16への通信はすべてlocalに向かうという意味だ。
他の通信は? まだ定義されていない。
4つのサブネットはすべて同じルートテーブルの設定になっていた。

とりあえずこのままEC2を立ててみよう。

EC2を起動する。

一旦10.0.0.0/24 hoge-a-publicにEC2を起動します。
基本はデフォルトオプション。 パブリックIPは有効にしました。

$ ssh -i .ssh/my_aws_key.pem ec2-user@xxx.xxx.xxx.xxx

と、つながらない。igwが設定されていないので、これは閉じたサブネットになっているからか。 igwを設定していきます。

ということで失敗パターンでした。

インターネットゲートウェイを作成

名前をつけて作成します。
作成したインターネットゲートウェイVPCにアタッチするだけの簡単なお仕事です。
これで晴れてこのVPCはインターネットに繋がることができました。

ただ、まだVPC自体にigwをアタッチしただけで、ルートテーブルには設定していないので、igwとサブネットが分断されています。

ルートテーブルを作成

ルートテーブルはプライベートサブネット用とパブリックサブネット用に2つ作成します。
既存のルートテーブルは f:id:benzenetarou:20190202182740p:plain だったので、プライベートサブネットでははこちらを引き続き利用します。
パブリックサブネットでは、これに加えて、10.0.0.0/16以外の通信はインターネットゲートウェイに通じてほしいので、igwを追加したルートテーブルを作成します。

Create route tableします。
以下のような状態にします。

f:id:benzenetarou:20190202185638p:plain

このパブリックサブネット用のルートテーブルが作成できたら、パブリックサブネットのルートテーブルをこれに変更します。

EC2にssh接続

先程の状態ではssh接続できなかったEC2が、果たして接続できるようになっているのかっ?
$ ssh -i .ssh/my_aws_key.pem ec2-user@xxx.xxx.xxx.xxx

できました!勝ち!!

ルートテーブル完全に理解しました。

ここまで約1時間! ちょっとかかり過ぎな気もするけど、どこかでアホミスをしていたわけではないので、及第点とします。
デフォルトで何がどこまで作成されるのかを整理できたので良かった!

2019/02/02 19:16

EC2出の細かい設定

Amazon Linux 2でPHP7.2をインストールする - Qiita php7.2用にamazon-linux-extrasを使う。

sudo yum update - y
sudo yum install -y httpd
sudo amazon-linux-extras install php7.2
sudo yum install -y php php-mbstring php-pdo php-mysqln
sudo vi /etc/httpd/conf/httpd.conf

以下のように変更

<IfModule dir_module>
    DirectoryIndex index.php index.html
</IfModule>
sudo yum install -y git
git clone https://github.com/ketancho/udemy-aws-14days.git
cd udemy-aws-14days/Day3
sudo cp -r . /var/www/html
sudo chkconfig on
sudo service httpd start

コンテンツ表示は本質ではないので、udemu作者さんが書いたサンプルコードを利用させてもらいます。 これでxxx.xxx.xxx.xxxにアクセスします。 とりあえず、表示されましたー

ここまで約40分。ちとかかり過ぎかな?

2019/02/02 21:27

プライベートサブネットにDBを作成する

プライベートサブネットにDB用のサーバーを作成します。

自分のローカルPC(グローバル)から直接はssh接続できないので、エージェントフォワードでssh接続したい。
鍵の情報をサーバーの中においておくのはあまりよろしくないと思うので、この方法を採用。

sshエージェントフォワード

ssh-agentを利用して、安全にSSH認証を行う - Qiita
ちょっとよくわからなかった。

ssh agent forwardingを行うサーバー側の要点の備忘録 - Qiita
これはわかりやすかった。

Macでやること

# 鍵の登録
ssh-add .ssh/my_aws_key.pem

# 確認
ssh-add


# 踏み台サーバーにssh接続
$ ssh -i .ssh/my_aws_key.pem ec2-user@xxx.xxx.xxx.xxx

$ sudo  vi /etc/ssh/ssh_config

# AllowAgentForwarding yes がコメントアウトになっているので外して有効化

$ exit


# macから。鍵の指定は必要ない!
$ ssh -A ec2-user@xxx.xxx.xxx.xxx

# プライベートサブネットのサーバーにssh接続
$ ssh aaa.aaa.aaa.aaa 

# 接続できましたー888888

NATゲートウェイを使用してプライベートサブネットから通信可能にする

$ sudo yum update -y

をしようとすると、インターネットに繋がっていないので、失敗する。 パブリックサブネットにNATゲートウェイを作成しました。
プライベートサブネットに適用されているルートテーブルの0.0.0.0/0の向き先を作成したNATゲートウェイに変更します。

その後

$ sudo yum update -y

をすると、正常に通信できているようです!やった!

mysqlを入れます。

sudo yum install -y mysql57-server
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
パッケージ mysql57-server は利用できません。
エラー: 何もしません

おいおいおいおい。
AWSのEC2で行うAmazon Linux2(MySQL5.7)環境構築 - Qiita
Amazon linux2はやはりいろいろと勝手が違うことが多いようだ。
これをみるに、まずは

l$ amazon-linux-extras list
  0  ansible2                 available    [ =2.4.2  =2.4.6 ]
  2  httpd_modules            available    [ =1.0 ]
  3  memcached1.5             available    [ =1.5.1 ]
  4  nginx1.12                available    [ =1.12.2 ]
  5  postgresql9.6            available    [ =9.6.6  =9.6.8 ]
  6  postgresql10             available    [ =10 ]
  8  redis4.0                 available    [ =4.0.5  =4.0.10 ]
  9  R3.4                     available    [ =3.4.3 ]
 10  rust1                    available    \
        [ =1.22.1  =1.26.0  =1.26.1  =1.27.2  =1.31.0 ]
 11  vim                      available    [ =8.0 ]
 13  ruby2.4                  available    [ =2.4.2  =2.4.4 ]
 15  php7.2                   available    \
        [ =7.2.0  =7.2.4  =7.2.5  =7.2.8  =7.2.11  =7.2.13 ]
 16  php7.1                   available    [ =7.1.22  =7.1.25 ]
 17  lamp-mariadb10.2-php7.2  available    \
        [ =10.2.10_7.2.0  =10.2.10_7.2.4  =10.2.10_7.2.5
          =10.2.10_7.2.8  =10.2.10_7.2.11  =10.2.10_7.2.13 ]
 18  libreoffice              available    [ =5.0.6.2_15  =5.3.6.1 ]
 19  gimp                     available    [ =2.8.22 ]
 20  docker=latest            enabled      \
        [ =17.12.1  =18.03.1  =18.06.1 ]
 21  mate-desktop1.x          available    [ =1.19.0  =1.20.0 ]
 22  GraphicsMagick1.3        available    [ =1.3.29 ]
 23  tomcat8.5                available    [ =8.5.31  =8.5.32 ]
 24  epel                     available    [ =7.11 ]
 25  testing                  available    [ =1.0 ]
 26  ecs                      available    [ =stable ]
 27  corretto8                available    [ =1.8.0_192  =1.8.0_202 ]
 28  firecracker              available    [ =0.11 ]
 29  golang1.11               available    [ =1.11.3 ]
 30  squid4                   available    [ =4 ]

で確認してみるとな。
なるほど、このリポジトリ?にメジャーなミドルウェアとかは用意されている的なことなんだろう。
しかし今回はmysqlはその中になかったと。
なのでこの記事では、自前で公式mysqlリポジトリを追加しようとしている。
自分はepelの文字列を確認できたのでepelから落としてみようかと思う。

sudo amazon-linux-extras install -y epel
yum --enablerepo=epel search mysql
.
.
.

よくわからない結果が出てきたので、やめる。
というよりは、epelにはmysql-serverはなさそうだった。remiにはあるっぽいけど、どうせremiの追加が必要ならmysqlの公式使うわ。

# リポジトリの追加
sudo yum localinstall https://dev.mysql.com/get/mysql80-community-release-el7-2.noarch.rpm -y

#mysql8.0リポジトリの無効化
sudo yum-config-manager --disable mysql80-community

#mysql5.7リポジトリの有効化
sudo yum-config-manager --enable mysql57-community

#mysql5.7がインストールできるか確認
$ yum info mysql-community-server


#mysqlインストール
sudo yum install mysql-community-server -y
mysqld --version

# 起動設定&start
sudo service mysqld start
sudo chkconfig mysqld on

AWSのEC2で行うAmazon Linux2(MySQL5.7)環境構築 - Qiitaを途中までやっていたけど、mysqlのセットアップは本質的なところじゃないから、今は必要なかったんだよなーそこでの頑張りは。

#rootパスワードを確認
$ cat /var/log/mysqld.log | grep password
A temporary password is generated for root@localhost: ************

ここで表示されたパスワードをそのまま使用しようとしたら、ログインはできたけど、create databaseできなかった。再作成しないとだめだったらしい。

mysql_secure_installation

パスワード変更。パスワード様式の要求が面倒くさかった。Mysql 5.7* パスワードをPolicyに合わせるとめんどくさい件について - Qiita

mysql -u root -p

create database simple_blog;

use simple_blog;

create table posts (id int not null primary key, title varchar(100), detail varchar(1000), image varchar(1000));

insert into posts values (1, "XXXX", "XXXXXXXXXX", "./img/img1.jpeg");

insert into posts values (2, "YYYY", "YYYYYYYYYY", "./img/img2.jpeg");

grant all privileges on *.* to root@"%" identified by 'PASSWORD_hogehoge' with grant option; 

webサーバー側からmysql -h x.x.x.x -u root -pでDBサーバーに接続するとのことだったけども、

$ mysql -h 10.0.1.35 -u root -p
-bash: mysql: コマンドが見つかりません

そりゃそうだ。。入れてないもんそんなもの。

おとなしく旧バージョンのOSでやればよかった。 webサーバーを作り直す。

旧バージョンのOSで作り直した方は、ssh-agentでうまく行かず
もういいや鍵コピーする。

諸々できましたー
途中面倒くさくなって省いたけど、プライベートサブネット内のDBサーバーにパブリックサブネットのWebサーバーからアクセスして、グローバルIPでアクセスできました!!

振り返り

ドメインとのヒモ付はまだ完了していないが、一通りのVPC周りのことは、特にルートテーブルあたりは整理できて腑に落ちた。
一応形としては、パブリックサブネットからプライベートサブネットに通信するということができたので、良しとする。

しかし、本質的じゃないところで変に躓いてしまった。これは良くないところとして反省しよう。
全部をきれいに、正しいやり方でやりたがりすぎるきらいがある。
やるべきときはそれをやって、本質じゃないところではとりあえず動けばいい、迂回策でいい。この考えをして手を動かしていかないと、永遠に終わらない。

docker-composeのコンテナ内でcronを動かせたけど、railsのrakeタスクが実行できない

昨日、コンテナ内でcronを実行すること自体は成功したのだが、どうもrailsアプリのrakeタスクを実行する際に、(おそらく)権限の問題で実行できていなかったので、それを修正する。

目標は1時間以内(本当は30分で終わらせたいところ)

まずは現状を確認。 containerにexecする。ホストにて。

$ docker-compose exec app bash

rootでbashにログインできました。私はrootです。

# whoami
root

crobntabを確認する

crontab -e 

→なにもない。(コメントがありますが、有効な記述はない。)

次に、/etc/cron系を調べてみます。

/etc/crontab

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --reporrt /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --reporrt /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --reporrt /etc/cron.monthly)

これはデフォルトで入っていたやつでした。

他の/etc/cron系のファイルを見てみても、(見てないですが)なさそうだったので、特にcron系は設定されていない状態なはずです。

ここから、一つずつ動作を確かめていきませう。

進め方を決めます。
テスト環境にて
1. crontabの動作を確認する(権限必要ないファイルに書き込み)
2. railsのdummyのrakeタスクを実行.ログをファイル出力
3. 本番のrakeタスクを実行。
4. Dockerfile変更→build→run→cronの動作確認
5. 本番に反映

テスト(ローカル)環境と本番環境をごっちゃにしていたので、どこのcrnotabが有効かわからなくなってしまっていた。

テスト環境のコンテナのcronの状況確認

crontab -l
* * * * *   echo "Hello $(date)" >>/var/log/cron.log 2>&1
* * * * *   /usr/local/bundle/bin/rake status:testman  >>/var/log/cron.log 2>&1
* * * * *   pwd >> /var/log/cron.log 2>&1

一番上を確認する。 これ以外は一旦コメントアウトにする。

* * * * *   echo "Hello $(date)" >>/var/log/cron.log 2>&1

出力先をきれいにしておきます

rm -rf /var/log/cron.log

cron実行想定時間後

# cat  /var/log/cron.log
Hello Wed Sep  5 14:59:01 UTC 2018

OKです。ファイルの書き込み(なければ作成)もOKでした。 以下のcronだけを残します。

* * * * *   /usr/local/bundle/bin/rake status:testman  >>/var/log/cron.log 2>&1

ちなみにrakeタスクのstatus:testmanは以下

desc "test"
task :testman => :environment do
    File.open("db/hoge.csv","a") do |f|
    f.puts(Time.now)
    end
end

コレ自体はちゃんと動作することは確認した。

しかし、cronが動かない。 出力先の/var/log/cron.logを見てみると、

/usr/bin/env: 'ruby': No such file or directory

予想: rakeタスクのパスがなんかどっかおかしいんだろ?

それっぽいstack overflow
やっぱりそれっぽいことを言われている。
$HOMEにpathを追加すれば行けるよ的なこと言っているが、あんまりいじりたくない。いじったらその分Dockerfileもいじらなくてはいけなくなるので、厄介。絶対厄介。

まずコンテナ内のrubyのパスを確認してみる。

# which ruby
/usr/local/bin/ruby

...しかしこれがわかって何になるのか。


bundle execすればいい話?
ちょっとdocker-composeのマウントとかにいろいろ紛れてvendorの場所をあまり意識していなかったが、それか?

printenvで表示されるpathに
/usr/local/binは含まれているので、rubyを探せる。

あ、よし。
cronにprintenvさせればいいのだな。

HOME=/root
LOGNAME=root
PATH=/usr/bin:/bin
SHELL=/bin/sh
PWD=/root

やはり、pathがなかった。
正当な解決までの道のりは遠そうだったので、次の方針で進める。
cronでsh /opt/hoge.sh >> /var/log/cron.log
として、
/opt/hoge.shファイルにて

export PATH=/usr/local/bundle/bin:/usr/local/bundle/gems/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
/usr/local/bundle/bin/rake status:testman

これで行けそう。

# sh /opt/cron.sh

これは成功した!
次に、cronから呼び出す。

だめ。
/var/log/cron.log

rake aborted!
No Rakefile found (looking for: rakefile, Rakefile, rakefile.rb, Rakefile.rb)

おっとこれは、rakeファイルを実行する場所が悪かったようでした。
/opt/cron.sh

export PATH=/usr/local/bundle/bin:/usr/local/bundle/gems/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
cd /webapp
/usr/local/bundle/bin/rake status:testman

場所を移動して実行するように変更.

rake aborted!
Bundler::GemNotFound: Could not find rake-12.3.1 in any of the sources
/webapp/config/boot.rb:3:in `<top (required)>'
/webapp/config/application.rb:1:in `require_relative'
/webapp/config/application.rb:1:in `<top (required)>'
/webapp/rakefile:4:in `require_relative'
/webapp/rakefile:4:in `<top (required)>'
(See full trace by running task with --trace)

わからん。
bundle execしないとだめってこと?
一旦それでやってみるよ?
/opt/cron.shを以下に変更

export PATH=/usr/local/bundle/bin:/usr/local/bundle/gems/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
cd /webapp
/usr/local/bin/bundle exec rake status:testman

結果、だめ。
/var/log/cron.log

bundler: failed to load command: rake (/usr/local/bundle/bin/rake)
Bundler::GemNotFound: Could not find rake-12.3.1 in any of the sources
  /usr/local/lib/ruby/site_ruby/2.5.0/bundler/spec_set.rb:91:in `block in materialize'
  /usr/local/lib/ruby/site_ruby/2.5.0/bundler/spec_set.rb:85:in `map!'
  /usr/local/lib/ruby/site_ruby/2.5.0/bundler/spec_set.rb:85:in `materialize'
  /usr/local/lib/ruby/site_ruby/2.5.0/bundler/definition.rb:171:in `specs'
  /usr/local/lib/ruby/site_ruby/2.5.0/bundler/definition.rb:238:in `specs_for'
  /usr/local/lib/ruby/site_ruby/2.5.0/bundler/definition.rb:227:in `requested_specs'
  /usr/local/lib/ruby/site_ruby/2.5.0/bundler/runtime.rb:108:in `block in definition_method'
  /usr/local/lib/ruby/site_ruby/2.5.0/bundler/runtime.rb:20:in `setup'
  /usr/local/lib/ruby/site_ruby/2.5.0/bundler.rb:107:in `setup'
  /usr/local/lib/ruby/site_ruby/2.5.0/bundler/setup.rb:20:in `<top (required)>'
  /usr/local/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
  /usr/local/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'

これはなんだ。

これは/otp/cron.shが悪い。
そもそも、bashからsh /opt/cron.shを実行しても同様のエラーになるので、cornの罠とかじゃなく、bundle exec とかは必要ないはず。

ってかそもそもbundle execなしで普通にbashからrakeタスク実行できていたので、同じrootユーザーなのでできるはず。
考えよう。

考えてもわからん。
無能すぎて泣ける。
明日は時間トレなさそうなのでもう諦める。
むしろ一回一回コンテナを起動してしまうことは甘んじて受け入れて、ホストのcronで不なコンテナを消し込みに行こうかな。

全然解決できなさすぎて、セーブポイントから結構進めてたどり着く最初の中ボスみたいなの10回くらい倒せなくて諦めた小5くらいにやってた犬夜叉のゲーム思い出したわ。

docker-composeのコンテナ内でcronを動かす

nginx+rails+PostgreSQLなアプリをdocker-composeで作っていた。
railsのアプリケーションでcronで実行したいrakeタスクがあった。
このタスクのcron化自体はrails(ruby)のgemで行うのだけど、うまく行かなかった。
原因としては、そのgemも結局は、railsアプリ内ではなくそれがのっているホスト(この場合はコンテナ)のcronの仕組みを使うためのラッパーのようなものなので、そもそもコンテナでcronが使えなきゃしょうがない。
結局調べてみたところ、rubyのimageはdebianがベースイメージとして使われていて、そのベースイメージではcronはなかった。
なので、apt-get installして使える必要にしてから、rails側でもcron化するgemを使用すべきなのだけど、面倒くさかったので強硬策に出ていた次第だ。
その方法とは、コンテナのホストから、コンテナごとcronで起動してしまう方法。
これでなんとか凌いでいたのだが、問題が発生。
ホストからcronのためだけにいちいちコンテナを起動して、終わったらそのままにしていたので、使い終わったコンテナがたくさん溜まっていくという現象が発生してしまった。 あくる日容量エラーになってしまったのだった。。。

これは良くないと思い、ちゃんとcronを使っていく。

まずは、動作確認のために、コンテナ上においてcronをinstallして使えるかどうか試してみる。

docker-compose.ymlはこんな感じ。 railsのコンテナでcronを使いたいので、以下のコマンドでシェルに接続で(言葉あってるのか?)

$ docker-compose run app bash

まずはcronをinstallする

# apt-get install cron

install成功したので、早速使ってみる。

# crontab -e
no crontab for root - using an empty one
update-alternatives: error: no alternatives for editor
/usr/bin/sensible-editor: 25: /usr/bin/sensible-editor: editor: not found
/usr/bin/sensible-editor: 28: /usr/bin/sensible-editor: nano: not found
/usr/bin/sensible-editor: 31: /usr/bin/sensible-editor: nano-tiny: not found
/usr/bin/sensible-editor: 34: /usr/bin/sensible-editor: vi: not found
Couldn't find an editor!
Set the $EDITOR environment variable to your desired editor.
crontab: "/usr/bin/sensible-editor" exited with status 1

おっと、editorがない。そんなことがあるもんなんだな。 サクッとインスコしてみる。 適当にそれっぽいコマンドを想像して叩く。

# apt-get install vim

インスコできました。

1 * * * * echo hoge >> /var/log/cron/echo

と書いて保存。
しかし、そもそも/var/log/cronというディレクトリがなかった。

mkdir /var/log/cron

作ってみるも、なしのつぶて。 誰が悪いのか。

と、単に自分のcronがうんこだっただけだった。 以下のcronで。

* * * * *   echo "Hello $(date)" >>/var/log/cron.log 2>&1

いやしかし、これも動かない。 どうやらcrontab -eに問題がありそうだ。

/etc/crontabを編集 以下を追加

* * * * *   echo "Hello $(date)" >>/var/log/cron.log 2>&1

これもだめ、そもそもcronのサービスが起動していないんじゃないか。 それを調べよう。
redhut系で言うところのserviceはdebianでいうなんだっけか。
と思い調べ、結局わからず

# service cron status

と適当に売ってみたら、

[FAIL] cron is not running ... failed!

と表示された。
なんだdebianもserviceで良かったのか。

startさせます。

# service cron start
[ ok ] Starting periodic command scheduler: cron.

よっしゃ成功! 1分待ってcronの実行を確認します。

実行されていました。
くぅ〜これにて完了です。

と言いたいところだが、これをdocker-compose.ymmlに書く。

としたいが、タイムオーバーで、これは明日にやることにする
ここまではローカルのdocker環境で実行していたので、これか本番で一旦cronをコンテナー内に仕込む

そして明日Dockerfileを変更する

laravelのFacadeとserviceクラスがわからない

永遠にわからない。

Ruby on Rails出身の僕。
flaskはわかった。というより、むしろflaskを勉強したことでRuby on Railsの理解も深まった。
flaskだとスクラッチに近い形で書かないと行けないところを、Ruby on Railsだとそこらへんをフレームワーク側ですでに提供してくれている。
これはなんて便利なものなんだと改めて感じた。
flaskではMVCの全部を1つのファイルに収めることもできる。しかしそれは全体が見にくくなるので、普通に作るならやらない。
MVCのありがたみみたいなものも深く理解できた気がする。

しかし、laravelおまえときたら。
Facadeとは何ぞや。Serviceクラスとはなんぞや。Serviceコンテナーとはなんぞや。
新しい概念が多くて納得感がないまま早2ヶ月くらい。
コントローラーが肥大化してきてロジックの流れが見えづらくなるので、部分的に切り出して外にまとめる。という考え方はわかる。
この考え方がFacadeに近いはずだ。
これにあとはDIとかが入ってきて、より便利にラップしてくれて、呼び出し側からすれば単一のシンプルなインターフェースで呼び出せて、裏側ではよしなにやってくれている。
自分が集めた情報だとこんな感じだ。
それはわかるんだが、Serviceクラスとかをかませる理由がわからない。
Laravelのフレームワークがそういう仕様だといえばそれまでだが、そうなのだとしてなぜそんな仕様にしているのか。そうすることで何が嬉しいのか。これが腑に落ちない。 肥大化したコントローラーを切り出して、Facadeにまとめる。それをコントローラーから呼び出す。
これだけでいいじゃないか。なぜServiceというものを挟む必要があるのか。

そもそもFacadeを呼び出す際にいろいろ準備?が必要ということか?
ちゃんとテストコードを書いたことがないために、テストのために想定される実装・機能についてのイメージが湧かないことがこの非理解の原因だろうか。
テストをしやすいようにもっくとかすたぶとかをうまいことやる必要がある。らしい。 それらを呼び出したりするのがServiceクラスということになるのだろうか。

うーむ。来月あたりの自分に、この頃の自分頭わっるぅと言わしめたい。
ということでわからない備忘録終了。

プロセスとスレッドとかタイムスライスとかスケジューリングがわからない

nginxをかませると何がいいのか? その答えを探して、先輩に聞いてみたところキーワードとして「プロセス、スレッド、cpuコア」みたいな話がふんわりと出てきて、しかし自分の方での知識がなさすぎ説明の土台に立てていなかったので、一旦そこら編を整理しておく。

プロセスらへんについては、「ふつうのLinuxプログラミング」という本で読んで、メモリとかうまいことやってくれているというふうに理解した覚えがある。
それぞれのプロセス同士が使うメモリを競合しないように、うまいことやっていると。
一つのプロセスがメモリを使う際には、実際の物理メモリアドレスはどこなのかを意識しない、自分だけのものとして使っていいと、それを誰かが調整してくれている。みたいな認識だった。

http://moro-archive.hatenablog.com/entry/2014/09/11/013520

を読んで、あーそんなこと言ってたなそういえば。と思い出した。

マルチプロセスは、メモリ空間とかごと別の環境ができると。
そのぶん、マルチプロセス内で処理を切り替えるときにはプロセスごとにいちいち仮想アドレスと物理アドレスマッピングをしなきゃいけないと。
なので、その分オーバーヘッドが生じて遅くなる...? 一方でマルチスレッドの場合は、スレッド間でメモリのデータセグメントは共有されている?ので、その分いちいちマッピングをする必要はないので、オーバーヘッドはなくなる?みたいな?

しかしそもそもプロセスなんてのはサーバー上に走っているのでどっちにせよマッピングとかはプロセスが切り替わるごとにやらなきゃいけないんじゃないのか?

いやーわかんない。

そもそも、2つのリクエストがほぼ同時に来たときに、レスポンスを返すための処理がとても重いとする。それをマルチスレッドでさばくと、全体としてのレスポンスの速さは変わるの?
というもの、スレッドが複数個走っているときにも、プロセスはひとつなので、スケジューリングされるタイムスライスの時間は変わらない?はず。とすれば、同じ計算能力に対して仕事が2倍になるのだから、それぞれの仕事の進捗の速さは2分の一になってしまうじゃないか。
というかタイムスライスはどうやって決まるんだ?マルチスレッドで2つのスレッドがたった時点で、タイムスライスが2倍になってくれば問題はないのだが、そこらへんまで踏み込むのは今はやりすぎな気がしている。

明日聞いてみよう。

やっぱりもうちょっと調べてみた。

タイムスライスの長さをシステム負荷に応じて動的に調整します まず、全タスクを一周動作させ るまでのスケジューリング周期を求めます。この値は単一プロセッサ時の周期 (20msec) に CPU 数によって 重み付けをした値です

CPU 数が増える程スケジューリング周期は長く、プロセスが増える程タイムスライスは短くなりま す。これは、CPU 数が増えれば一度に実行出来るプロセス数が増えるのでスケジューリング頻度を増やす必 要が無くなる為です。

https://syuu1228.github.io/process_management_and_process_schedule/process_management_and_process_schedule.pdf

この説明では、プロセスごとにタイムスライスを多めに割り振る方法とかはない。
しかし、topコマンドで負荷を見ながら、yesコマンドを打つと大量にCPUが食われているのがわかる。
ということはやはり処理に応じてタイムスラスは割り振られているんだなぁ。

nginxとかrailsとかdocker-composeの備忘録

nginxのインストール

$ sudo yum update -y
$ sudo yum install -y nginx
$ nginx -v 
nginx version: nginx/1.12.1
$ sudo service nginx status
nginx is stopped
$  sudo service nginx start
Starting nginx:                                            [  OK  ]

xxx.xxx.xxx.xxxにアクセス。
(セキュリティグループで80番ポートを開放しておく)

無事あのページが表示されました。
f:id:benzenetarou:20180728001121p:plain

コンテンツの変更

nginxのドキュメントルートはここらしい /usr/share/nginx/html

参考: [備忘録]nginxのドキュメントルート

ということで、ドキュメントルートのコンテンツを変更してみる。

$ ls -al /usr/share/nginx/html/
合計 24
drwxr-xr-x 2 root root 4096  7月 27 15:06 .
drwxr-xr-x 3 root root 4096  7月 27 15:06 ..
-rw-r--r-- 1 root root 3696  9月 11  2017 404.html
-rw-r--r-- 1 root root 3738  9月 11  2017 50x.html
-rw-r--r-- 1 root root 3770  9月 11  2017 index.html
-rw-r--r-- 1 root root  368  9月 11  2017 nginx-logo.png
lrwxrwxrwx 1 root root   32  7月 27 15:06 poweredby.png -> /usr/share/pixmaps/poweredby.png

特にパスを指定しない場合は、index.htmlが表示される。

$ sudo vi /usr/share/nginx/html/hoge.html

このファイルにhogeと書き込んでおく。
そしてxxx.xxx.xxx.xxx/hoge.htmlにアクセスする。

すると、hogeと表示された。

さて、これで /usr/share/nginx/html/hoge.htmlがドキュメントルートということが確認できたが、この設定ファイルはどこに書いてあるんだろうか。

設定ファイルを探してみる。 どうやらたくさんあるみたいだ。

# ls -al /etc/nginx
total 76
drwxr-xr-x  4 root root 4096 Jul 27 15:06 .
drwxr-xr-x 79 root root 4096 Jul 27 15:06 ..
drwxr-xr-x  2 root root 4096 Jul 27 15:06 conf.d
drwxr-xr-x  2 root root 4096 Sep 11  2017 default.d
-rw-r--r--  1 root root 1077 Sep 11  2017 fastcgi.conf
-rw-r--r--  1 root root 1077 Sep 11  2017 fastcgi.conf.default
-rw-r--r--  1 root root 1007 Sep 11  2017 fastcgi_params
-rw-r--r--  1 root root 1007 Sep 11  2017 fastcgi_params.default
-rw-r--r--  1 root root 2837 Sep 11  2017 koi-utf
-rw-r--r--  1 root root 2223 Sep 11  2017 koi-win
-rw-r--r--  1 root root 3957 Sep 11  2017 mime.types
-rw-r--r--  1 root root 3957 Sep 11  2017 mime.types.default
-rw-r--r--  1 root root 3734 Sep 11  2017 nginx.conf
-rw-r--r--  1 root root 2656 Sep 11  2017 nginx.conf.default
-rw-r--r--  1 root root  636 Sep 11  2017 scgi_params
-rw-r--r--  1 root root  636 Sep 11  2017 scgi_params.default
-rw-r--r--  1 root root  664 Sep 11  2017 uwsgi_params
-rw-r--r--  1 root root  664 Sep 11  2017 uwsgi_params.default
-rw-r--r--  1 root root 3610 Sep 11  2017 win-utf

たくさんあって、どこがそれらしいか探すのが面倒臭そうだ。
とりあえず現在は/usr/share/nginx/html/がドキュメントルートになっているので、その文字列をこのディレクトリ以下から再帰的に検索して探す。

【Linux】複数あるファイルの中から特定の文字列を検索するコマンド | かわたま.net より、いい感じのコマンド。

# find ./* -type f -print | xargs grep 'nginx/html'
./nginx.conf:        root         /usr/share/nginx/html;
./nginx.conf:#        root         /usr/share/nginx/html;

見つかった。
結局モロに一番怪しそうな/etc/nginx/nginx.confにあった。 コメント行をエスケープして見てみると、そこまでバカ多くなさそうで安心。

$ cat nginx.conf | grep -v -E "^\s*#" | grep -v -E "^\s*$"
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
    worker_connections 1024;
}
http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;
    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;
    include /etc/nginx/conf.d/*.conf;
    index   index.html index.htm;
    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  localhost;
        root         /usr/share/nginx/html;
        include /etc/nginx/default.d/*.conf;
        location / {
        }
        error_page 404 /404.html;
            location = /40x.html {
        }
        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

試しにlisten 80 default_server;の部分を80→8081に変えてみる。

sudo service nginx configtest
sudo service nginx restart

すると、ポート指定なしでアクセスできず、8081ポートでアクセスできた。なるほど。

nginxとプロセスとかの話

nginxをかませることのメリットは、大量のアクセスをさばけるだとか、リダイレクトやらなんやらができるみたいな認識だ。
聞きかじった知識では、プロセスがコアの数だけうんぬんみたいな話だった。
まずは、nginxを挟まないとプロセスが1個だけだよ〜ってのを確認したい。
pythonの軽量フレームワークのflaskで確認する。

これの環境構築は

benzenetarou.hatenablog.com

ここにサクッと書いてある。
今回は、hello.pyを以下のようにする。

import time
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello'

@app.route('/sleep')
def sleep():
    time.sleep(2)
    return 'sleep'

if __name__ == '__main__':
    app.run(host='0.0.0.0')

これで、起動する。

python36 hello.py
 * Serving Flask app "hello" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

これをローカルからcurlする。 2つのシェルスクリプトを用意する。
sleep.sh

echo `date '+%y/%m/%d %H:%M:%S'`
echo ""
for i in `seq 0 99`
do
    curl http://xxx.xxx.xxx.xxx:5000/sleep
done
echo ""
echo `date '+%y/%m/%d %H:%M:%S'`

loop.sh

echo `date '+%y/%m/%d %H:%M:%S'`
echo ""
for i in `seq 0 99`
do
  curl http://xxx.xxx.xxx.xxx:5000
done
echo ""
echo `date '+%y/%m/%d %H:%M:%S'`

これをターミナルを2つ開いて同時に実行させる。 ちなみにloop.shだけを実行すると、

18/07/28 01:39:14

HelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHello
18/07/28 01:39:20

となった。(使っているwifiは36.4Mbpsをマークしていた)

もしflaskが1リクエストずつしか返せないとしたら、sleep()の処理をしている間はhello()の処理もできないので、sleepの遅さに引きずられてloopも遅くなるはず。

実行結果は、 sleepがちんたらやっている間、loopはloopで勝手にリクエスト&レスポンスを繰り返していて、通常時と殆ど変わらない速さで終了した。

と、いうことは? Flaskはnginxとかかまさなくても、複数の処理を同時にできる。と。
あっれーnginxいらなくね?ってなってしまったので、ちゃんと調べる。

qiita.com

オイオイオイオイホントかよ。。

この記事が言うことにゃあ "デフォルトでは"一つのリクエストのレスポンスを返すまでは、次のリクエストの処理を始められないとな。
ソースコードまんま引用させていただきます。

from flask import Flask, make_response, jsonify
import time

# flask
app = Flask(__name__)

# rest api
@app.route('/hello/<string:value>', methods=['GET'])
def hello(value):
    result = 'hello {0}'.format(value)
    print('[start] ' + result)

    time.sleep(10)

    print('[end] ' + result)
    return(make_response(result))

# main
if __name__ == "__main__":
    # 同時アクセスができない(並列処理が不可能)
    # app.run(host='localhost', port=3000)

    # 同時アクセスができる(並列処理が可能)
    app.run(host='localhost', port=3000 threaded=True)

オイオイオイオイ、あいつ死ぬぜ。
結果、どうやら仕様が変わったようで、デフォルトではthreaded=Trueになっているようでした。
逆にthreaded=Trueと指定することで、リクエストからレスポンスを返すまでが詰まると、どんどんキューが溜まっていくという挙動を確認できた。

ただ、これにnginxをかませてもボトルネックはアプリケーション・サーバーのflaskになると思うので、nginxをかませる利点がまだ腑に落ちない。
本でも買ってみるかな。

と、そもそもなんでこんなnginxについて調べているのかを見失っていた。。。

いいのや、別にC10K問題とか、個人開発では直面せんやろ…
直面できたらラッキーだわむしろ。
ということで、これはタイムカプセル案件にしておく。

本来やりたかったことは、
nginx + unicorn + rails (puma?)をdocker-composeでec2上でデプロイすること。
その前段として、dockerを使わないで上記のデプロイ環境を構築すること。
そして、その前段としてnginxの概要を理解することだった。
いままでapacheですら簡単にしか触ったことなかったし、その使い方も、適当にドキュメントルートをルートディレクトリとして、localのmac動かす感覚でrails -s コマンド叩くみたいな多分間違っているやり方だったっと思う。
ので、ちゃんとウェブサーバーとアプリケーションサーバーの間の設定とか理解しときたい。
一旦、本来やりたいnginx + unicorn + railsをやりにいく。

実際にインストール

https://qiita.com/eighty8/items/0288ab9c127ddb683315 とりあえずこの手順をほとんど思考停止設定していけば、「nginx + unicorn + rails + mysql on docker」が構築できて、実際にサーバーにポート指定なしでアクセスできて、scaffoldまでできる。

これで実行環境は良しとして、課題は
1. 現行動いているソースコードをどうやってここに反映するか
2. この手順ではmysqlを使っているが、現行のシステムではpostgresqlを使っているので、その差異をどうやって吸収するか
3. そもそもDBをどうやって移行するか

この3点を解決したい。
といったところで、明日にする。

railsのactiverecordでlastの一つ手前を取得したい。secondの逆をやりたい。

$ rails c
# ユーザー取得
u = User.find(1)

# ユーザーのtweetを取得したい
u.tweets.content

# ユーザーのtweetの最新を取得したい
u.tweets.last.content

# ユーザーのtweetの最初のを取得したい
u.tweets.first.content

# ユーザーのtweetの最初2個目を取得したい
u.tweets.second.content

# ユーザーのtweetの最新の一つ前を取得したい
u.tweets.last(2)[0].content

u.tweets.last(2).content だと構文エラーになる罠があるので注意。
u.tweets.last(2)で取得できるのは、tweetオブジェクトの配列なので、要素指定してあげないとだめ。