Rail5.1 rc1+Dockerでいい感じの環境を構築する
目的
いい感じに使える、Dockerの開発環境を構築する。 最終的な目的はProductionでDockerを使うイメージを固めること。
環境
docker for mac はいい噂を聞かないのでVagrant上でDockerを使用する。 当然、docker for mac でも動く。
Railsアプリ作成
bundle init
gem 'rails', '~> 5.1.0.rc1'
bundle instlal --path vendor/bundle
sprockets, turbolinksを抜いています。 DBはmysqlを使用
bundle exec rails new --skip-bundle -d mysql --skip-sprockets --skip-turbolinks .
ローカルにbundle設定があるとダメなので消します。
rm -rf vendor/bundle rm -rf .bundle
Docker install
sudo apt-get update sudo apt-get install \ linux-image-extra-$(uname -r) \ linux-image-extra-virtual sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable" sudo apt-get install docker-ce
dockerグループが出来ているので、vagrantユーザーをdockerグループに追加する必要があります。 その後、vagrantからlogoutし再度sshで入ります。
sudo gpasswd -a vagrant docker
docker-compose install
sudo curl -L "https://github.com/docker/compose/releases/download/1.11.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose
Dockerfile
Dockerfileを作成します。 alpine linuxをベースにRuby2.4.1の入っているイメージを元にします。 必要なパッケージをインストール後、不要なファイルを消したりします。 mysql2 gem をbuildするために必要なmariadb-devを入れるとmariadb本体も付いてきてしまうため、mariadb本体のファイルを削除しています。
cmd, entrypointはrun時に指定するので付けていません。
Dockerfile
FROM ruby:2.4.1-alpine ENV LANG ja_JP.UTF-8 ENV BUILD_PACKAGES="curl-dev build-base" \ DEV_PACKAGE="mariadb-libs mariadb-client mariadb-client-libs tzdata" RUN gem install bundler \ && apk --update --upgrade add $BUILD_PACKAGES \ && apk add mariadb-dev tzdata linux-headers postgresql-dev sqlite-dev git nodejs \ && rm /usr/lib/libmysqld* \ && echo 'gem: --no-document' > /etc/gemrc WORKDIR /app COPY Gemfile Gemfile COPY Gemfile.lock Gemfile.lock RUN bundle install EXPOSE 3000
docker-compose.ymlを作成します。
spring server起動用のコンテナをwebと同一Dockerfileから作っています。 springについては以下を参考にさせていただきました。
参考
https://github.com/jonleighton/spring-docker-example http://tech.degica.com/ja/2016/06/14/dockerized-rails-development/
user指定時に、ホストOSのユーザーIDとグループIDを指定している。 ユーザーを指定しない場合、rootで実行されるため、Railsコマンドで生成したファイルがrootの所有となって、ホストOSのエディタから編集が出来なくなる。
version: '2' services: db: image: mysql:5.7.17 volumes: - ./store:/var/lib/mysql environment: MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' web: build: . command: bundle exec rails s user: "${uid}:${gid}" working_dir: /app environment: RACK_ENV: 'development' volumes: - .:/app ports: - "3000:3000" depends_on: - db spring: build: . command: docker/run.sh spring volumes: - .:/app
mysqlの設定
※追記 mysqlコンテナを用意するようにしたのでこちらは不要になりました。
DockerコンテナからホストOSのMySQLに接続するためにホストOS側のMySQLの設定を修正します。
sudo vi /etc/mysql/my.cnf
bindするアドレスに0.0.0.0を追加します。
bind-address = 127.0.0.1 bind-address = 0.0.0.0
MySQLを再起動します。
sudo service mysql restart
bindアドレス同様に、 rootアカウントがlocalhostからしかログインできないためユーザーを追加します。
mysql -u root CREATE USER 'root'@'172.17.0.2'; GRANT ALL PRIVILEGES ON *.* TO 'root'@'172.17.0.2'; FLUSH PRIVILEGES;
開発
docker-compose系のコマンドを叩くのが面倒な為、scriptを用意しています。
- 引数なしでヘルプ表示
- 第一引数がupなら、docker-compose up
- 第一引数がrspecならRACK_ENV=testでrspec起動
- 第一引数がsならフォアグラウンドでRails起動 port指定
- 第一引数がspringならspringサーバーをバックグラウンドで起動
- 引数ありの場合は引数をそのまま渡す。
docker/bundle.sh
#!/bin/bash ENV=$RACK_ENV export uid=$UID export gid=$GID if [ "$ENV" = 'production' ]; then docker-compose run --entrypoint=$entrypoint -e RACK_ENV=$ENV web s exit 0 fi entrypoint=docker/run.sh if [ "$1" = 'up' ]; then docker-compose up elif [ "$1" = 'rspec' ]; then docker-compose run --entrypoint=$entrypoint -e RACK_ENV=test web $@ elif [ "$1" = 'spring' ]; then docker-compose run -u "$UID:$GID" --entrypoint=$entrypoint -d spring spring elif [ "$1" = 's' ]; then docker-compose run -p 3000:3000 -u "" --entrypoint=$entrypoint -e RACK_ENV=development web s else docker-compose run -u "$UID:$GID" --entrypoint=$entrypoint -e RACK_ENV=development web $@ fi
docker/run.sh
alpine linuxにはbash等が入っていなく、わざわざ入れる必要もないコードなのでデフォルトのシェルであるashで書いています。
#!/bin/ash if [ "$RACK_ENV" = 'development' -o "$RACK_ENV" = 'test' -o "$RACK_ENV" = '' ]; then if [ $# -eq 0 ]; then bundle exec rails -h elif [ $1 = 'rspec' ]; then bundle exec $@ elif [ $1 = 'spring' ]; then bundle exec spring binstub --all #bundle exec spring binstub --remove --all bin/spring server else time bundle exec rails $@ fi exit 0 fi bundle exec unicorn -c config/unicorn.rb -E $RACK_ENV
deploy
deploy時に使用するDockerfileを作る
FROM ruby:2.4.1-alpine ENV LANG ja_JP.UTF-8 ENV BUILD_PACKAGES="curl-dev build-base" \ DEV_PACKAGE="mariadb-libs mariadb-client mariadb-client-libs tzdata" RUN gem install bundler \ && apk --update --upgrade add $BUILD_PACKAGES \ && apk add mariadb-dev tzdata linux-headers postgresql-dev sqlite-dev git nodejs \ && rm /usr/lib/libmysqld* \ && echo 'gem: --no-document' > /etc/gemrc WORKDIR /app COPY Gemfile Gemfile COPY Gemfile.lock Gemfile.lock RUN bundle install ENV APP_HOME /app RUN mkdir -p $APP_HOME WORKDIR $APP_HOME COPY . $APP_HOME EXPOSE 3000
circle_ciでbuildして、testを実行。 testが通ったら、imageをECRなり、DockerHubなり、GCRへpushする。
まとめ
当初、DockerfileでアプリのソースをCOPYしてイメージを作っていたが、 ソースを含めてイメージを作るのはdeploy時だけでいいのでやめた。 軽く開発してみたが上手く進められている。 deploy周り(CricleCI含め)を妄想しかしていないので、いざやってみたらまた変わるかもしれない。
問題点が1つあって、user指定で実行するとbundler関係のディレクトリがroot所有なのでwarningが出る。 解決策が思いつかない。どなたかいい解決策があれば教えてください。
`/` is not writable. Bundler will use `/tmp/bundler/home/unknown' as your home directory temporarily.
今回のソースは以下にあります。 https://github.com/astapi/development_rails_with_docker
terraform で aws のいい感じの構成を作る(基盤編1)
対象読者
terraformが何かを知っていて、 terraformを使おうと考えている人。
terraform version 0.9.1
初期設定
適当にディレクトリを作成します。
mkdir terraform_test cd terraform_test
0.9.1からstate environmentsを設定できるようになったので、 dev, stg, prod みたいに同一ソースで分岐ができるようになる。
vagrant-ubuntu-trusty-64% terraform env new dev Created and switched to environment "dev"! You're now on a new, empty environment. Environments isolate their state, so if you run "terraform plan" Terraform will not see any existing state for this configuration.
env new をすると、作成したenvに移る。 gitのbranchみたいだな。
vagrant-ubuntu-trusty-64% terraform env list default * dev
AWS 設定
provider "aws" { region = "ap-northeast-1" }
variable "aws_account_id" { default = "1111111111" }
IAMアカウントの作成 AWSのIAMベストプラクティスをちゃんとやる(見る) 上記エントリに沿って、初期設定を行う。
resource "aws_iam_user" "astapi" { name = "astapi" path = "/" } resource "aws_iam_access_key" "astapi" { user = "${aws_iam_user.astapi.name}" }
plan
IAMのベストプラクティスでは、ルートアカウントのアクセスキーとか消さないといけないのだけど、 terraformでIAMユーザーから作りたいので、最初だけルートアカウントのキーを使う。
export AWS_ACCESS_KEY_ID="anaccesskey" export AWS_SECRET_ACCESS_KEY="asecretkey" export AWS_DEFAULT_REGION="ap-northeast-1"
planコマンドで作成予定のリソースを確認する。
terraform plan 中略 + aws_iam_access_key.astapi encrypted_secret: "<computed>" key_fingerprint: "<computed>" secret: "<computed>" ses_smtp_password: "<computed>" status: "<computed>" user: "astapi" + aws_iam_user.astapi arn: "<computed>" force_destroy: "false" name: "astapi" path: "/" unique_id: "<computed>"
apply
terraform apply
IAMユーザーができました。
terraform show
このコマンドで作成したアクセスキーの情報が閲覧できます。
vagrant-ubuntu-trusty-64% terraform show aws_iam_access_key.astapi: id = AKJLAKJDA secret = aslkfjaslfkjasljfas;ljfalskjdfalsjkfak ses_smtp_password = asdklfjasldkfjasdkfjasldjkfas;l status = Active user = astapi aws_iam_user.astapi: id = astapi arn = arn:aws:iam::1111111111:user/astapi force_destroy = false name = astapi path = / unique_id = AAKLJALKFJAKDJALKJD
ポリシーのアタッチ
ルートアカウントを使わなくてもterraformで全ての権限が必要なので、AdministratorAccessが必要。
resource "aws_iam_policy_attachment" "admin" { name = "admin-policy" users = ["${aws_iam_user.astapi.name}"] policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" }
今後は作成したIAMユーザーのACCESS_KEYを使って実行していきます。 ルートアカウントのACCESS_KEYを削除しましょう。
まとめ
terraform でIAMユーザーのアクセスキーまで作成したが、terraform showでキーが参照できるため、 複数人開発ではこの方式だとNGになりますね。 また、Backend(terraformで作成したAWSの状態を保存しておくところ)がlocalのため、 そもそも自分のローカルでしか実行が出来ない。 backendとしては色々用意されているが、S3 backendでは、terraform envに対応していなかった為、 counsul等、自前サーバーを用意したりする必要がありそう。 そのconsulサーバーを用意するterraformが必要・・・。 うーん。
プログラミングを魔法だと思っていた
昔、すごいプログラムだったりすごいシステムを魔法のように感じていた。 自分には全くどうやっているのか想像がつかなかった。 今からすると、なぜ魔法だと思っていたのかなっと思って、考えてみた。
フレームワークなんて、よくわからずにアクションを書いたら、 なぜかテンプレートに移動して、テンプレートを書くとブラウザにHTMLが表示された。 特定のURLを叩いたときに、特定のActionが呼ばれる理由もわからない。 そこにあるのは命名ルールだけだった。 特に疑問も持たずにルールに沿ってプログラムを書いて、 なんとなく画面が表示されてた。 プログラミングは辛かった。 もともとあるプログラムをコピペしていじって、なんとなく作っていた。 新しいものは作れない。自分にはわからない魔法だから。
ここまで読んでわかったヒトもいると思うけど、おれが触っていたのはJavaだった。 正直いまでもJavaはわからない。あれ以降やってないから。 Javaが苦手だったのは、外部ライブラリのコードが読みにくいから。 jarに固められた状態で手元に渡ってきて、その状態でなんとなく使う。 ドキュメントもあんまりないイメージだった。 jarという圧縮ファイルのブラックボックスだった。 たまに、逆コンパイルでコードを読んだりもしたけど、全然楽しくなかった。 なんで、わざわざ固められているものを戻して、解読みたいなことしてんだろって。 たぶん、これは自分の知識不足なだけで、きっと、元ソースもどっかにあったんだろうし、きっと、ドキュメントも探せばあったんだと思う。Javadocが。
そういう経緯があって、phpを触ったときはコードを奥深くまで読み進められるのが嬉しかった。 この頃から少しずつ、魔法ではなくなってきたかもしれない。
あるときちゃんと魔法に向き合おうと思ってコンピュータサイエンスとかhttpの仕様とか学ぶようになった。 そこからは、いろいろなことがクリアになったと思う。 蓋を開けてみれば、魔法だと思っていたことが実は泥臭いコードの塊だったり、 簡単なプログラムをうまく組み合わせることで魔法のように見えていただけだったりした。
魔法が魔法ではなかったと気づいた時から、仕組みがわからないものは仕組みを理解するように心がけるようになった。 そうした事を続けていると面白いことに、初めて触るライブラリやツールでも、 こういう機能がたぶんあるはず。こういう風に使うといいはず。 内部的にはたぶんこうなってるんだろうな。 みたいな事がなんとなくわかるようになっていた。 今はプログラミングはすごく楽しい。
自分のように、わけもわからずコードを書いている人がこの世にはまだいっぱいいると思う。 そういう人たちにも、プログラミングが楽しめるようになってほしい。
AWSのIAMベストプラクティスをちゃんとやる(見る)
IAM のベストプラクティスをちゃんと理解しようというお話。
目的
このエントリでは、細かいやり方は書かない。 (ドキュメントを見ればいいし、コンソールを開けばわかる) こういうことをしたほうがいいんだよ。という内容を把握し、いざ、必要な時がきたときに知ってるいる状態にすることが目的
対象読者
- AWSはEC2とかたてたりするけど、*1ルートアカウントでコンソールをいじってる人
- EC2にIAMインスタンスプロファイルのロールを付けるとは??な人
- 権限とか難しいから、フルアクセス付けてるよ?な人
IAMとは?
AWS Identity and Access Management (IAM)
の略
アイアムって読んでます。(正しいかは知らない)
IAMを使うと、どんな事ができるのか
- ルートアカウント以外にアカウントを作成できる。
- 作成したアカウントに対して、*2リソースごとにアクセスを制限できる
ルートアカウントを共有しない。 フルアクセス権限を全員に渡さない。 誰が何のリソースをどこまで操作できるか。を制限しよう。 というのがIAMを使う理由の認識です。
やること
AWS アカウント(ルート)のアクセスキーをロックする
ルートアカウントはすべての権限があるため、 ルートアカウントの権限を用いて、AWSコマンド等を使用すると、請求情報まで取得できる。 見られて困るものは、見れないようにしましょうね。という話し
- アクセスキーを持ってるなら削除する
- IAMで、管理権限を持つユーザーを作成する。
- ルートアカウントのパスワードは強度の高いものを設定しよう。
- ルートアカウントにMFAを設定する。
個々の IAM ユーザーを作成する
AWSにアクセスする必要がある場合は、IAMでユーザーを作成する。 IAMユーザーのアクセス許可は、いつでも、有効、無効にすることができる。 ※ルートアカウントは他人に絶対に教えてはいけない。 たとえ、個々のIAMユーザーに管理権限を付与するとしても、 ルートアカウントでない限りはアクセスを制限できるので、 必ず、IAMユーザーを都度、作る。
AWS 定義のポリシーを使用して可能な限り権限を割り当てる
ポリシーとは AWSではアクセス権限を制御するのにポリシーという定義を利用する。
ポリシー例
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": [ "aws-portal:*Billing", "aws-portal:*Usage", "aws-portal:*PaymentMethods", "budgets:ViewBudget", "budgets:ModifyBudget" ], "Resource": "*" }] }
AWS内でデフォルトで定義されているポリシーを使って可能な限り、権限を割り当てる。 メリットとしては、簡単。 デメリットとしては、権限の単位がざっくりしている。
例 - AdministratorAccess - フルアクセス権限 - AWSCloudTrailReadOnlyAccess - ClourTrailの読み取り権限
職務によって、ポリシーがAWSで定義されている。 職務機能の AWS 管理ポリシー
IAM ユーザーへのアクセス許可を割り当てるためにグループを使います。
ポリシーをIAMユーザー一人ひとりに付けるのは面倒だし、 複数人いても大体同じ権限になる。という場合に、グループを使う。
ここらへんで、単語が増えてきて混乱してきたと思うので、 以下にユーザ、ポリシー、グループの関係を整理。
- グループ作成
- グループにポリシーをアタッチ(付与】
- ユーザーをグループに追加
ポリシーはユーザーに直接アタッチも可能。
最小限の特権を認める。
まずは、最小限のアクセス(特定サービスへの権限、リードオンリー)を割当て、 必要になった際に権限を解放していくようにしよう。という話し
Access Advisorを使う アクセスアドバイザーは、コンソールでIAMユーザーを見る場合にタブの1つである。 該当ユーザーが、権限が付与されているサービスにいつアクセスしたかがわかる。 この項目を参照し、1度もアクセスをしていないサービスがあれば、 その権限はそのユーザーには不要な可能性があるため、 権限の見直しに使える。
ユーザーのために強度の高いパスワードポリシーを設定する。
IAMユーザーのパスワードに、パスワードポリシーを設定できる
特権ユーザーに対して、MFA を有効化する。
AWS での多要素認証 (MFA) の使用 ルートアカウント同様に、重要なリソースへの権限を持っているユーザーにはMFAを設定しようという話し。 例えば、(どのサービスでも)削除権限を持ってる人とか。
Amazon EC2 インスタンスで作動するアプリケーションに対し、ロールを使用する。
EC2上から別AWSサービスにアクセスすることはよくある。 そのアクセス権限は適切に定義しよう。という話し
ロールとは、AWSのリソースにアクセスできる権限の役割の定義
また新しい言葉が出てきたので、整理する。
- IAM ポリシー
- AWS上で扱う権限の一番小さい単位
- IAM ユーザー
- AWSにアクセスするユーザーの単位
- グループに含めたり、ポリシーをアタッチして権限を付与する
- IAM グループ
- 複数のポリシーをまとめたもの
- 複数のユーザーに同一の権限を与えるために使う
- IAM ロール
認証情報を共有するのではなく、ロールを使って委託する。
さっきのロールの使い方での他AWSアカウントへは、アクセスキーを共有するんじゃなくて、 ロールを使おうね。という話し
認証情報を定期的にローテーションする。
定期的に認証情報を更新しようという話し
不要な認証情報の削除
使われていない認証情報があったら消そう。という話し
認証情報レポートや、awsコマンドで最後に使用した日付とかがわかるので、 それを見て、消す対象を精査できる。
追加セキュリティに対するポリシー条件を使用する。
ポリシーに対して、IPアドレスの制限が時間の制限を行うことができるので、 可能な限り、制限したほうがいいよ。という話し
AWS アカウントのアクティビティの監視
- cloudfront
- cloudtrail
- cloudwatch
- aws config
- S3
の、アクセスログを利用して、不正なアクセス、利用と監視しようという話し
まとめ
全部やるのは過剰なのかな?という気もしますが、 そこは自分たちのやり方に合わせて、うまくやっていくのがいいのかなと思います。