20190915のAWSに関する記事は22件です。

AWS secrets.ymlからcredentials.ymlへ

目次

  1. はじめに
  2. secrets.ymlとは?
  3. credentials.ymlとは?
  4. 具体的な変更
  5. 変更によって変わった手順
  6. 参考にしたwebサイト、書籍
  7. 参考になりそうなwebサイト、書籍

はじめに

私は、プログラミングをの勉強を初めてまだ2ヶ月の初心者であります。自分のアウトプットも兼ねてまだまだ技術は未熟ではありますが、私以外の初めて間もない方々の役に立つようなことができればと思いまして、投稿をさせていただきます。不備等ございましたら、恐れ入りますがコメントにてご指摘いただければ幸いでございます。

secrets.ymlとは?

Rails5.2より前のバージョンにおいて、秘匿情報(secret_key_baseや外部APIのアクセスキー等)を記述していたファイルのことです。このファイルにはデフォルトでアプリケーションのsecret_key_baseが含まれていました。

credentials.yml.encとは?

Rails5.2以降のバージョンにおいて用意された秘匿情報を保存しておくための新しいファイルです。Rails5.2からは暗号化されたcredentials.yml.encを扱います。しかし、secret.ymlも使うことはできるので、アップグレードの際にすぐに用意しなければならないというわけではありません。
また、credentials.yml.encの内容は暗号化されています。復号のやり方としては、master keyを利用して復号化することができます。デフォルトのmaster keyはconfig/master.keyを参照しましょう。編集の際は、rails credentials:editコマンドを使います。Rails5.2において生成される.gitignoreには、.config/master.keyが標準で含まれているので、gitリポジトリに誤って入れてしまうのを防ぐことができるようになっています。

具体的な変更点

  • config/credentials.yml.encを追加し、secrets.ymlの記述の際のように、各環境ごとに設定値をそれぞれ分ける仕様ではなくなった
  • 開発やテストのシーン等、秘匿情報がの復号化の必要ない場面では求められなくなった 以上の2点が大きく変わった点かと思われます。

参考にしたwebサイト

AWS利用上のセキュリティを確保する方法
【Rails5.2】credentials.yml.encとmaster.keyでのデプロイによる今までとの変更点
Rails5.2から導入されたcredentials.yml.encを極める
Rails5.2から追加された credentials.yml.enc のキホン
Rails5.2からsecrets.yml*が廃止されcredentials.yml.encに統合されるよ
credentials.yml.encによるアクセスキーの管理方法
Rails セキュリティガイド
「Rails5.2」 credentials.yml.encを生成する方法
Railsのcredentials.yml.encは、どういった運用ができるか?
credentials.yml.encを環境ごとに使い分ける【Ruby on Rails】
Rails5.2 暗号化(Credentials)関連の単語メモ
Rails 5.2 の credentials.yml.enc に登録してあるデータを呼び出す方法
Rails の config/credentials.yml.enc を使ってみる
Rails5.2のcredential管理を試してみた
credentials.yml.enc やめたい…
Rails 4.1 の secret.yml とは…
Rails 5.2の本番デプロイ時に secrets.yml でハマった
secrets.ymlや環境変数をRails 5.2のEncrypted Credentialsに移行する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2上Redhatのリポジトリの有効化、無効化

リポジトリ一覧

yum repolist all
yum repolist all | grep enabled

epleを有効化

yum-config-manager --enable epel

epelを無効化

yum-config-manager --disable epel
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【受験記録】AWS Cloud Practicioner(CLF-C01)

前提

  • SIer勤務3年目
  • アプリケーション開発者
  • インフラはほぼ分からない
  • クラウドのメリット、デメリットはなんとなくわかる。
  • 受験日 2019/9/13

試験概要

問題数:65問
時間:100分
合格点:700点/1000点
- 問題は選択肢形式のみで、4つの選択肢から1つ、もしくは5つの選択肢から2つを選ぶのみでした。画面が与えられて選ぶ形式もなかったです。
- その他の問題は一度回答した後でも、戻って振り返り、変更をすることができました。画面で"後でチェックする"というボタンもあり、すべて回答後、チェックを付けた問題がどれかわかるようになっていました。

セクション名
- クラウドの概念(28%)
- セキュリティ(24%)
- テクノロジー(36%)
- 請求と料金(12%)

勉強方法

  1. AWS Cloud practicioner Essentialsを視聴。
  2. AWS認定資格試験テキスト AWS認定 クラウドプラクティショナーを1周読み込み。
  3. 以下2冊の本を読み込み。(AWS SAA資格以降の本です。)

感想

  • 各サービスについての理解は深くなくても大丈夫。(AのサービスはBするために使うという感じで)
  • コストや管理に関する部分はもう少し自分で勉強したほうがよかったと思う。
  • 難易度は前回のAZ900よりは難しく感じた。
  • 1か月以内にはSAA資格もとりたいなぁ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2から起動されたAMIを特定する

コマンド

$ curl http://169.254.169.254/latest/meta-data/ami-id

実行結果

ami-00b95502a4d51a07e

探す

コンソールからIDで検索
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSでk3sクラスタを構築する(手動編)

AWS EC2でk3sクラスタを構築する土台を作ります。
k3sの場合「動かしてみた」は本当にコマンド一つで完了してしまいますが、
ここでは自動セットアップやスケーリングなどを視野にいれたちょっとちゃんとしたものを作ります。

最終的には起動テンプレートやAWS CDK (CloudFormation)を使ってコードとしてまとめることを見越した上で
本記事では手動でAWSコンソールやsshをポチポチして作ってみます。

公式提供スクリプト風のことをやってみる

k3sは公式でk3sセットアップ用のスクリプト ( https://get.k3s.io/ ) を用意しています。
そのまま使うとそこそこいい感じにしてくれるので、これを参考にセットアップしていきます。

スクリプトをざっと読む

スクリプトで実行していることをまとめると、ざっくり以下のことをしています。

  • k3sバイナリのダウンロード
  • バイナリの設置・symlink作成
  • 運用系スクリプトの設置(killall, uninstall)
  • systemd/openrcサービス関連ファイル設置(古いものの削除・環境変数ファイル・サービスファイル)
  • サービスの起動

※スクリプト最下部のエントリポイントの関数呼び出しをまとめただけです

オプションなどの分岐が多かったり、設置するスクリプトやサービスファイルをヒアドキュメントで全部書いてあるなどして非常に長く見えますが、やっている事自体は非常にシンプルになっています。

実際に作ってみる

これらの内容のうち、本記事の想定でミニマルにセットアップする際に必要な作業は以下です:

  • k3sバイナリのダウンロード
  • バイナリの設置・symlink作成
  • systemdサービスファイル設置
  • サービスの起動

k3sのserver側(k3s master)1・agent側でそれぞれEC2インスタンスを2台用意し、セットアップしていきます。
なおEC2インスタンスはAmazonLinux2利用・VPCデフォルトセキュリティグループ使用(実際にはagent->serverに6443が通れば良い)の想定です。

※ここからは実際にサーバ構築を行いますが、都度作ったり消したりを繰り返すため、あらかじめ起動テンプレートを準備しておくと楽ができます。

server側

まずserver側のセットアップをshellコマンドにするとこんな感じです:

# バイナリダウンロード&設置
curl -L https://github.com/rancher/k3s/releases/download/v0.8.0/k3s -o /usr/local/bin/k3s
chown root:root /usr/local/bin/k3s
chmod +x /usr/local/bin/k3s

# symlink
for cmd in kubectl crictl ctr; do
  ln -s /usr/local/bin/k3s /usr/local/bin/$cmd
done

※k3sのバージョンは自分が試したときの最新です

つぎにsystemdのサービスファイルを設置します。

/etc/systemd/system/k3s.service
[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
After=network-online.target

[Service]
Environment=K3S_KUBECONFIG_MODE=644
ExecStart=/usr/local/bin/k3s server --disable-agent
ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay

KillMode=process
Delegate=yes
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target

設置したらサービスを起動します。

systemctl daemon-reload
systemctl start k3s

本記事ではserver側はagent無効にしています。systemdサービスファイルの中身は公式スクリプトをほぼ流用しています。

またEnvironment=K3S_KUBECONFIG_MODE=644を指定していますが、これを指定することで非rootユーザでkubectlを実行できます。
root権限でしかkubectlしない想定ならなくても問題ないです。

k3sサーバが起動できたらnode-tokenを取得します。/var/lib/rancher/k3s/server/node-tokenをcatするなりしてコピーしておきます。

agent側

次にagent側をセットアップしますが、作業内容はserver側とほぼ同じでk3sの起動コマンドと環境変数が異なるのみです。

systemdのサービスファイルからEnvironmentとExecStartの行を消し、以下を追加した版でセットアップします:

Environment=K3S_URL=https://{server側マシンのローカルIP}:6443
Environment=K3S_TOKEN={コピーしておいたnode-token}
ExecStart=/usr/local/bin/k3s agent

問題なければこれでagentが起動・クラスタに加入するはずなので、実際に確認します。server側インスタンスで以下を実行します。

$ kubectl get node
NAME                                               STATUS   ROLES    AGE   VERSION
ip-172-31-xx-xxx.ap-northeast-1.compute.internal   Ready    worker   41s   v1.14.5-k3s.1

SSM ParameterStoreを使う

作業としては簡単にk3sクラスタが出来上がりましたが2、node-tokenの取得・反映のところに手作り感が拭えません。
せっかくAWSを使っているので、SSM ParameterStoreを介してこの部分を自動化します。

スクリプトを用意する

期待する挙動は「server起動後にIPとtokenを書き込み、agent起動前にそれらを環境変数として読み込み」ですが、systemdではそれぞれExecStartPostとExecStartPre、EnvironmentFileで実現できます。3

そこでまずExecStartPre/Postで実行するスクリプトを用意します。

server側スクリプト
#!/bin/bash

ip=$(hostname | cut -d'.' -f1 | cut -d'-' -f2,3,4,5 | tr '-' '.')
aws ssm put-parameter --region ap-northeast-1 --name /k3s/master/host --value "$ip" --type "String" --overwrite 

token=$(cat /var/lib/rancher/k3s/server/node-token)
aws ssm put-parameter --region ap-northeast-1 --name /k3s/master/token --value "$token" --type "String" --overwrite 

service起動後に実行される想定で、IPアドレスとnode-tokenをParameterStoreに保管しています。
IPアドレスはip addrあたりから取得するのが正当かと思いますが、プライベートIPに絞るのが面倒だったのでhostnameからshell芸で取得しています。

agent側スクリプト
#!/bin/bash

host=$(aws ssm get-parameter --region ap-northeast-1 --name /k3s/master/host | grep '"Value"' | sed 's|[^:]*: "\([^"]*\).*|\1|')
echo "K3S_URL=https://${host}:6443" > /opt/k3s.env

token=$(aws ssm get-parameter --region ap-northeast-1 --name /k3s/master/token | grep '"Value"' | sed 's|[^:]*: "\([^"]*\).*|\1|')
echo -n "K3S_TOKEN=$token" >> /opt/k3s.env

service起動前に実行される想定で、ParameterStoreから取り出したIPアドレスとnode-tokenをEnvironmenFileの形式で書き出しています。
awscliの結果からValueを取り出したいのですが、jqコマンドが入っているわけでもないのでここでもshell芸で頑張っています。

これらをEC2インスタンス内に設置しserviceファイルに指定すれば動作するはずです。

systemd serviceに組み込む

このスクリプトを組み込んだ状態で動作テストを行いますが、基本は前にやった手順にスクリプト設置を差し込む差分があるだけです。

server側では

  • systemd serviceのServiceセクションにExecStartPost=/opt/k3s_env_put.shを足す
  • service起動前に、前述のスクリプトを/opt/k3s_env_put.shとして設置&実行権限を付与しておく

です。その手順で実行すると、k3s起動後にParameterStoreに値が書き込まれているはずです。4

次にagent側は

  • systemd serviceのServiceセクションにExecStartPost=/opt/k3s_env_setup.shEnvironmentFile=/opt/k3s.envを足す
  • service起動前に、前述のスクリプトを/opt/k3s_env_setup.shとして設置&実行権限を付与しておく
  • /opt/k3s.envを空ファイルでよいのでtouchコマンドなどで作成しておく

です。EnvironmentFileに指定したファイルが存在しない場合、サービスの起動自体が失敗してしまうので予め作るだけ作っておきます。

うまくいけば、これでagentも正しく起動するはずです。

EC2のuserdataに流し込んで自動化する

ここまでできれば、初期セットアップはすべてshell scriptで完結できるようになっています。

つまり、EC2インスタンス起動時にすべてをuserdataスクリプトとして設定しておけば、ただインスタンスを起動するだけですべてが完了します。

投入するスクリプト

server側とagent側でスクリプトは違いますが、半分くらいは同じなので部分ごとに記載します。
利用時はmergeしてください。

server側特有の部分
cat > /opt/k3s_env_put.sh << 'EOF'
#!/bin/bash

ip=$(hostname | cut -d'.' -f1 | cut -d'-' -f2,3,4,5 | tr '-' '.')
aws ssm put-parameter --region ap-northeast-1 --name /k3s/master/host --value "$ip" --type "String" --overwrite 

token=$(cat /var/lib/rancher/k3s/server/node-token)
aws ssm put-parameter --region ap-northeast-1 --name /k3s/master/token --value "$token" --type "String" --overwrite 
EOF
chmod +x /opt/k3s_env_put.sh

service_section="
Type=notify
Environment=K3S_KUBECONFIG_MODE=644
ExecStart=/usr/local/bin/k3s server --disable-agent
ExecStartPost=/opt/k3s_env_put.sh
"
agent側特有の部分
cat > /opt/k3s_env_setup.sh << 'EOF'
#!/bin/bash

host=$(aws ssm get-parameter --region ap-northeast-1 --name /k3s/master/host | grep '"Value"' | sed 's|[^:]*: "\([^"]*\).*|\1|')
echo "K3S_URL=https://${host}:6443" > /opt/k3s.env

token=$(aws ssm get-parameter --region ap-northeast-1 --name /k3s/master/token | grep '"Value"' | sed 's|[^:]*: "\([^"]*\).*|\1|')
echo -n "K3S_TOKEN=$token" >> /opt/k3s.env
EOF
chmod +x /opt/k3s_env_setup.sh
touch /opt/k3s.env

service_section="
Type=exec
ExecStart=/usr/local/bin/k3s agent
EnvironmentFile=/opt/k3s.env
ExecStartPre=/opt/k3s_env_setup.sh
"

ExecStartPre/Postのスクリプトの設置とserviceの差分の定義を行っています。

スクリプト設置時のヒアドキュメントでは、区切り単語(正式な呼称がわかりませんがここではEOFのことです)をシングルクォーテーションで囲っておくことで不要な変数展開が行われないようにしています。

共通スクリプト
#!/bin/bash

K3S_VERSION=v0.8.0
BIN_DIR=/usr/local/bin

### ここにserver/agent特有の部分を入れる

curl -L https://github.com/rancher/k3s/releases/download/$K3S_VERSION/k3s -o $BIN_DIR/k3s
chown root:root $BIN_DIR/k3s
chmod +x $BIN_DIR/k3s

for cmd in kubectl crictl ctr; do
  ln -s $BIN_DIR/k3s $BIN_DIR/$cmd
done

cat > /etc/systemd/system/k3s.service << EOF
[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
After=network-online.target

[Service]
${service_section}
ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay

KillMode=process
Delegate=yes
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now k3s

まとめるとこれも長いように見えますが、ヒアドキュメントが長いだけで操作内容は大したことないです。

実際にやってみる

上記userdataを入れた状態でまずserver側をインスタンス作成し、すこし時間をずらしてagent側も作成します。

これまでの説明の備考にもある程度書いていますが、インスタンス作成時に設定されているべきことは

  • なにかしらsshする手段を用意する(動作確認用。22ポートを空ける or セッションマネージャ)
  • agent -> server のTCP 6443が空いている
  • EC2インスタンスからParameterStoreにアクセスできる(serverからput-paremeter, agentからget-parameter権限)
  • server/agentそれぞれのuserdata

となります。これでserver/agentを起動すると、特に追加作業を入れることなくk3sクラスタが出来上がっているかと思います。

まとめ

セットアップをスクリプト化してuserdataに入れることで、ほぼ自動化できそうな感じでk3sクラスタを構築できました。

まだk8s manifestを入れる手段を用意していなかったりnode-tokenが(SecureStringではない)ただのStringだったりしますが、
このままCloudFormationなどに記述していけばちゃんとクラスタにできそうです。


  1. server側というのは用語としてややこしいのですが、k3sコマンドに沿ってそのまま表記します。また、いわゆるサーバはEC2インスタンスと表記しておきます。 

  2. 実際にはmanifest適用方法を確保したり稼働させたいサービスによってEC2インスタンスのポートを開けたりALBを配置するなどあるかと思います。 

  3. EnvironmentFileはサービス起動のたびに評価されるため、ExecStartPreで動的に値をセットする運用が可能です 

  4. 実際に組み込んで動かす前に、EC2インスタンスに該当Parameterの読み書き権限がついたIAM roleを設定しておく必要があります。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AnsibleでEC2のAmazon Linux 2にNginxをインストールする方法の検討(古いJinja2でもOK)

普通にRPMパッケージをインストールする場合

普通は Ansible の yumモジュールを使えば、以下のように簡単に RPMパッケージをインストールすることができます。

roles/httpd/tasks/main.yml
- name: Install Apache HTTPD
  yum:
    name: httpd
    state: present

stateパラメータに「present」を指定することで、指定のパッケージがまだインストールされていなければインストールを実行し、すでにインストール済みなら何もしないで正常終了します。これは OSが Amazon Linux 2 でも CentOS でも同じように動作します。

Amazon Linux 2にNginxをインストールする場合

ところが同じ方法で Amazon Linux 2 に Nginx をインストールしようとしたら、以下ようにamazon-linux-extrasコマンドを使えと表示されて失敗しました。

To use, run
# sudo amazon-linux-extras install nginx1.12

しかしメッセージのとおりにコマンドを使ってインストールを実行するには、パッケージがインストール済みかどうかを判定する処理が必要になります。

一般的な方法

以下の方法が一般的な方法のようです。

  1. 1つめのタスクで yumモジュールを使って、指定パッケージのリストを取得する
  2. 結果をregisterで指定した変数に保存する
  3. 2つめのタスクのwhenで、インストール済みパッケージを変数から抽出する
  4. インストール済みパッケージが 0件なら shellモジュールを使ってインストールのコマンドを実行する
roles/nginx/tasks/main.yml
- name: Get Nginx package list
  yum:
    list: nginx
  register: pkg_list

- name: Install Nginx
  shell: amazon-linux-extras install nginx1.12
  when: pkg_list.results | selectattr("yumstate", "match", "installed") | list | length == 0

1つめのタスクで使用している yumモジュールのlistパラメータにパッケージ名を指定すると、yumコマンドの「yum list <パッケージ名>」と同等の動作になります。
出力はregisterで指定した変数pkg_listに格納され、以下のように取得したリストがresultsに格納されます。

pkg_list
    "pkg_list": {
        "changed": false, 
        "failed": false, 
        "results": [
            {
                "arch": "x86_64", 
                "envra": "1:nginx-1.12.2-2.amzn2.0.1.x86_64", 
                "epoch": "1", 
                "name": "nginx", 
                "release": "2.amzn2.0.1", 
                "repo": "amzn2extra-nginx1.12", 
                "version": "1.12.2", 
                "yumstate": "available"
            }, 
(略)
            {
                "arch": "x86_64", 
                "envra": "1:nginx-1.12.2-2.amzn2.0.1.x86_64", 
                "epoch": "1", 
                "name": "nginx", 
                "release": "2.amzn2.0.1", 
                "repo": "installed", 
                "version": "1.12.2", 
                "yumstate": "installed"
            }
        ]
    }

リストの最初の要素はyumstateの値が"available"なので、まだインストールされていないリポジトリ上のパッケージ(インストール可能)です。しかし最後のパッケージはyumstateの値が"installed"なので、これがローカルにインストール済みのパッケージです。

2つめのタスクで「selectattr」というフィルタを使って、yumstate"installed"と一致する要素だけをリストから抽出し、その件数が 0件かどうかをwhenの判定条件にしています。

  when: pkg_list.results | selectattr("yumstate", "match", "installed") | list | length == 0

AWS Cloud9で失敗

ところが Ansible のコントロールノードに AWS Cloud9 を使っていると、このタスクは失敗して以下のメッセージが表示されました。

The error was: template error while templating string: no filter named 'selectattr'.

AWS Cloud9 は RHEL6 ベースなので epel からインストールできる Jinja2 のバージョンが古く、「selectattr」フィルタが実装される前のバージョンでした。

改良した方法

そもそも「selectattr」フィルタを使っている目的は、yumモジュールで取得したパッケージリストからyumstateの値が「installed」のパッケージを抽出するためです。しかし最初からインストール済みパッケージだけのリストを取得することができれば、後からフィルタを使って絞り込む必要がなくなります。

これは yumコマンドなら「yum list installed <パッケージ名>」を実行すれば簡単に実現できるのですが、yumモジュールのlistパラメータに指定できる値は「installed」と<パッケージ名>が排他になっているらしく、両方を同時に指定すると意図した動作になりませんでした。

代わりに以下のように、yumモジュールにdisablerepoパラメータを追加してすべてのリポジトリへの参照を無効化することで、ローカルにインストール済みのパッケージ(installed)だけのリストを取得することができました。

roles/nginx/tasks/main.yml
- name: Get Nginx package list
  yum:
    disablerepo: "*"
    list: nginx
  register: installed_pkgs

- name: Install Nginx
  shell: amazon-linux-extras install nginx1.12
  when: installed_pkgs.results | length == 0

これで2つめのタスクのwhenでは、単純にリストが 0件なら未インストールと判定できるようになったので、「selectattr」フィルタが不要になりました。

  when: installed_pkgs.results | length == 0

結果的に古い Jinja2 でも正しく動作するようになっただけでなく、判定条件の単純化によってコードの可読性が向上し、リポジトリを参照しないため処理時間を短縮することができました。

参考: 別の方法(shellモジュールとyumコマンドを使う)

以下の方法でも AWS Cloud9 の古い Jinja2 で正しく動作します。ただし Ansible に警告されてしまうので上記の方法の方が良いのですが、shellモジュールを使ってパッケージリストを取得する場合の参考として記載します。

roles/nginx/tasks/main.yml
- name: Check if Nginx is installed
  shell: yum list installed nginx
  register: result
  failed_when: result.rc not in [0, 1]
  changed_when: false

- name: Install Nginx package
  shell: amazon-linux-extras install nginx1.12
  when: result.rc == 1
  1. 1つめのタスクで yumモジュールではなく、shellモジュールで yumコマンドを実行する
  2. 「yum list installed <パッケージ名>」を実行することで、インストール済みの指定パッケージだけのリストを取得する
  3. 結果をregisterで指定した変数に保存する
  4. 2つめのタスクのwhenで、yumコマンドの終了ステータスが 1なら未インストールと判定して、shellモジュールを使ってインストールコマンドを実行する

コマンド実行結果の参照方法

shellモジュールでコマンドを実行した場合、registerで指定した変数のrc要素にコマンドの終了ステータス(リターンコード)が保存されます。実際の値は実行したコマンドに依存しますが、「yum list」の場合は 1件以上のリストを取得できれば 0(正常終了)、0件なら 1(異常終了)になります。

2つめのタスクで「終了ステータスが 1であること」をwhenの判定条件にすることで、インストール済みの指定パッケージが存在しない場合にコマンドを実行することができます。

  when: result.rc == 1

今回は使用しませんが、コマンドの標準出力はstdout要素、標準エラー出力はstderr要素で参照することができます。

エラーの発生を抑止する方法

しかしこれだけでは、yumコマンドの終了ステータスが 1の場合にタスクが失敗したと判断され、Ansible が実行を中断してしまいます。
そこで終了ステータスが 1になるのは意図した結果であることを Ansible に知らせるために、以下の定義を追加します。

  failed_when: result.rc not in [0, 1]

これは「失敗と判定する条件」を「コマンドの終了ステータスが[0, 1]に含まれない場合」とする定義なので、1が失敗から除外されます。

代わりに以下の記述でも Ansible が中断することを抑止することができますが、これは「失敗しても無視して実行を継続する」という指定なので、終了ステータス 1 が失敗と判断されること自体は変わりません。

  ignore_errors: true

今回のように 0以外の終了ステータス値が意図した結果として明確な場合は、failed_whenで明示的に失敗条件から除外するのが正しい方法です。

'changed'と判定されることを抑止する

shellモジュールでコマンドを実行すると Ansible は常に'changed'と判定してしまいますが、今回は「yum list」を使ってリストを取得しているだけなので、この判定を抑止します。以下の定義で Ansible が変化の判定をしないように指定します。

  changed_when: false

結果

パッケージがインストール済みの場合は何もしないでタスクが正常終了し、以下のように changed、failed ともに0件になりました。

PLAY RECAP *******************************************************************
target1                    : ok=2    changed=0    unreachable=0    failed=0

ただし以下のように、「yumコマンドを実行するより yumモジュールを使うべき」という警告を抑止することはできませんでした。

TASK [nginx : Check if Nginx is installed] ***********************************
 [WARNING]: Consider using the yum module rather than running yum.  If you ...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2とAmazon Elasticsearch Serviceを利用するときにド嵌った

概要

AWSでEC2とElasticsearch Service
をつなげようと思ったら意外と嵌ってしまった話です。

結構ec2上にelasticsearchを導入して利用している方は多いけど、Elasticsearch Serviceを立ててつなげる記事はあまりない、、、。

AWS EC2上にElasticSearchをインストール
AWSで怯えず作るEC2 + Amazon Elasticsearch Serviceでハマったところ

多少ネットに転がっているのですが、今をときめくLaravelを利用した記事がなかったので書いている次第。

利用環境

インフラ編

  • EC2(centOS7)
  • elasticsearch Service

動作環境

  • laravel5.8(php7.2)
  • elasticsearch Service 6.7
  • mysql8.0
  • nginx1.17

ローカル開発環境

ローカル開発環境はdockerを利用してelasticsearchのコンテナを立てて開発をしています。
laravelを使っているので、せっかくなのでscoutを利用。

ローカル上の.envの設定はこちら

.env
SCOUT_DRIVER=elasticsearch
ELASTICSEARCH_HOST=http://elasticsearch:9200

ELASTICSEARCH_HOSTのエンドポイントだけ変更してあげればいいんじゃん!

Elasticsearch ServiceとEC2をつないでみる。

※Elasticsearch Serviceマネジメントコンソール画面

ec2からcurlで叩いたら正常に動いている反応がある!

ec2
[centos@ip-***-**-**-* ****]$ curl https://エンドポイント.es.amazonaws.com
{
  "name" : "WNwU_Bn",
  "cluster_name" : "*********:********",
  "cluster_uuid" : "*****************",
  "version" : {
    "number" : "6.7.0",
    "build_flavor" : "oss",
    "build_type" : "zip",
    "build_hash" : "b8dfb4c",
    "build_date" : "2019-05-22T06:01:36.852084Z",
    "build_snapshot" : false,
    "lucene_version" : "7.7.0",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

つながっているということは

.env(ローカル)
SCOUT_DRIVER=elasticsearch
ELASTICSEARCH_HOST=http://elasticsearch:9200

.env(EC2)
SCOUT_DRIVER=elasticsearch
ELASTICSEARCH_HOST=エンドポイントのURL

に.envを書き換えれば行けるはず!

php artisan migrate:refresh --seedが通らない!

.envファイルを書き換えても一向に
php artisan migrate:refresh --seed
が通らない!
RDSともつないでいてそちらは正常に動いているのですが、
テストデータを入れているシーディングがどうも通らない。

UserTableSeeder.php
Artisan::call('scout:import', ["model" => User::class]);

エラーがでる箇所。

エラーメッセージ
Elasticsearch\Common\Exceptions\NoNodesAvailableException  : No alive nodes found in your cluster

調べて見ると、タイムアウトでこのエラーメッセージがでるからタイムアウトの時間を長くしてみてと書いてある

しかし、テストレコードは1レコードのみ。
タイムアウトになるはずもない。
相変わらずcurlは通る。
ローカル環境も問題なく動いている。
謎が深まり時間だけが過ぎていく、、、。

立ちはだかっていたのはport番号

思い当たる節もなく、PCとにらめっこ。
vendor以下のソースコードをひたすら追っていると変な記述が。

ClientBuilder.php
    /**
     * @throws InvalidArgumentException
     */
    private function extractURIParts(string $host): array
    {
        $parts = parse_url($host);

        if ($parts === false) {
            throw new InvalidArgumentException("Could not parse URI");
        }

        if (isset($parts['port']) !== true) {
            $parts['port'] = 9200;
        }

        return $parts;
    }

あれ?
port番号書いてないと勝手に9200番につなぐようにしている?
いやいや、、、、

流石にhttpsってついてたら443に繋ぐでしょ、、、、。

と追ってみるもそんな処理なし。

つまり、

.env(EC2)
SCOUT_DRIVER=elasticsearch
ELASTICSEARCH_HOST=エンドポイントのURL:443

とポート番号を記載する必要があるみたいです!

些細な設定ファイルで完全に嵌ってしまいました。
やっぱりちゃんとソースコードを追わないと行けないですね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2上のlaravel5.8とAmazon Elasticsearch Serviceを利用するときにド嵌った

概要

AWSでEC2とElasticsearch Service
をつなげようと思ったら意外と嵌ってしまった話です。

結構ec2上にelasticsearchを導入して利用している方は多いけど、Elasticsearch Serviceを立ててつなげる記事はあまりない、、、。

AWS EC2上にElasticSearchをインストール
AWSで怯えず作るEC2 + Amazon Elasticsearch Serviceでハマったところ

多少ネットに転がっているのですが、今をときめくLaravelを利用した記事がなかったので書いている次第。

利用環境

インフラ編

  • EC2(centOS7)
  • elasticsearch Service

動作環境

  • laravel5.8(php7.2)
  • elasticsearch Service 6.7
  • mysql8.0
  • nginx1.17

ローカル開発環境

ローカル開発環境はdockerを利用してelasticsearchのコンテナを立てて開発をしています。
laravelを使っているので、せっかくなのでscoutを利用。

ローカル上の.envの設定はこちら

.env
SCOUT_DRIVER=elasticsearch
ELASTICSEARCH_HOST=http://elasticsearch:9200

ELASTICSEARCH_HOSTのエンドポイントだけ変更してあげればいいんじゃん!

Elasticsearch ServiceとEC2をつないでみる。

※Elasticsearch Serviceマネジメントコンソール画面

ec2からcurlで叩いたら正常に動いている反応がある!

ec2
[centos@ip-***-**-**-* ****]$ curl https://エンドポイント.es.amazonaws.com
{
  "name" : "WNwU_Bn",
  "cluster_name" : "*********:********",
  "cluster_uuid" : "*****************",
  "version" : {
    "number" : "6.7.0",
    "build_flavor" : "oss",
    "build_type" : "zip",
    "build_hash" : "b8dfb4c",
    "build_date" : "2019-05-22T06:01:36.852084Z",
    "build_snapshot" : false,
    "lucene_version" : "7.7.0",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

つながっているということは

.env(ローカル)
SCOUT_DRIVER=elasticsearch
ELASTICSEARCH_HOST=http://elasticsearch:9200

.env(EC2)
SCOUT_DRIVER=elasticsearch
ELASTICSEARCH_HOST=エンドポイントのURL

に.envを書き換えれば行けるはず!

laravelとElasticsearch Serviceが連動しない!

.envファイルを書き換えても一向に
php artisan migrate:refresh --seed
が通らない!
RDSともつないでいてそちらは正常に動いているのですが、
テストデータを入れているシーディングがどうも通らない。

UserTableSeeder.php
Artisan::call('scout:import', ["model" => User::class]);

エラーがでる箇所。

エラーメッセージ
Elasticsearch\Common\Exceptions\NoNodesAvailableException  : No alive nodes found in your cluster

調べて見ると、タイムアウトでこのエラーメッセージがでるからタイムアウトの時間を長くしてみてと書いてある

しかし、テストレコードは1レコードのみ。
タイムアウトになるはずもない。
相変わらずcurlは通る。
ローカル環境も問題なく動いている。
謎が深まり時間だけが過ぎていく、、、。

立ちはだかっていたのはport番号

思い当たる節もなく、PCとにらめっこ。
vendor以下のソースコードをひたすら追っていると変な記述が。

ClientBuilder.php
    /**
     * @throws InvalidArgumentException
     */
    private function extractURIParts(string $host): array
    {
        $parts = parse_url($host);

        if ($parts === false) {
            throw new InvalidArgumentException("Could not parse URI");
        }

        if (isset($parts['port']) !== true) {
            $parts['port'] = 9200;
        }

        return $parts;
    }

あれ?
port番号書いてないと勝手に9200番につなぐようにしている?
いやいや、、、、

流石にhttpsってついてたら443に繋ぐでしょ、、、、。

と追ってみるもそんな処理なし。

つまり、

.env(EC2)
SCOUT_DRIVER=elasticsearch
ELASTICSEARCH_HOST=エンドポイントのURL:443

とポート番号を記載する必要があるみたいです!

些細な設定ファイルで完全に嵌ってしまいました。
やっぱりちゃんとソースコードを追わないと行けないですね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Deployment environment - Webアプリケーションの環境定義(本番/検証/開発/...) について考えてみる

はじめに

Webブラウザ や スマートフォンのアプリケーションと連動する Webアプリケーションの実装をよくやるんですが、フルスクラッチで数ヶ月(〜5ヶ月前後)の期間で構築する案件が多めで、その度に新規顧客 や 新しく発足したプロジェクトチームごとに環境(本番/検証/開発/...)の定義が違ってしまい、チームで共通認識を得るのに難儀します。

環境分割の前提が身についているメンバーなら良いですが、エンドポイントや環境固有パラメータをハードコーディングしてしまい、環境の向き先変更で毎度修正しなきゃならない(そうならないように、パラメータで向き先変更出来るような仕組み作りのサポートをしなければならない)ことも多々あるので、このあたりはお作法として情報がまとまっていて欲しいのですが、日本語の文献をあまり見つけることができませんでした。

こういう情報はプロジェクトやサービス、予算や組織ごとに検討・最適化されるものであってベストプラクティスとして定義するのも難しいのかもしれません。

近年の Webアプリケーションは .env などで環境依存のパラメータを渡すことで、コードは全環境同じものを使う前提であることが多いと思うので、アプリケーションの実装者は深く意識しなくても Web Application Framework のお作法に従っておけば大きな問題は発生しないと思いますが、システム全体の設計をしたり、プロジェクト全体を見通して テスト実行環境 や クライアント・アプリケーションとの接続 、 外部連携アプリケーション との連動を考慮しなければならない身としては、もう少し一般的で抽象化された情報がまとまっていて欲しいと考えていました。

最近、Terraform で AWSを使用したアプリケーションのプラットフォームを構築する手順を整理したのですが、ここでも環境分割の定義に手間取ったので、メモがてら理想的な環境分割の定義と用途がどういうイメージになるのか考えてみました。

書いてる人

  • IT業界8年目
  • PM から PG 、企画や営業支援など
  • OSSベースでWebアプリケーションを実装
  • Mobileアプリ(スマホアプリ)と連携
  • クラウドを使ったシステム基盤の構築( Terraform, CloudFormation)
  • フルスクラッチの受託開発多め(小型〜中型)

システムの構成 と アプリケーション

サーバ環境の変化

システムが稼働する環境はここ数年で大きく変わったように感じます。

私が IT業界に足を踏み入れた 2012年は クラウドという言葉はあっても、まだまだ サーバホスティング事業者の専有サーバや VPS がメインで使用されており、PaaS や mBaaS、 IaaS という概念はまだまだ一般的ではなかったように記憶しています。

また、過去を振り返ると、インターネットよりかなり前の時代は、大学や研究施設にある大型のコンピュータにターミナル接続して、そこのサービスを使用していたり、各企業の拠点にサーバルームがあって、基幹系のシステムはそういった場所から提供されるアプリケーションを利用していたというようなことを、何かの本で読みました。

この↑あたりの話がとくに、そういった時代のことだったと思います。

メインフレームや、クライアント独立のパーソナルコンピュータの時代から、90年代以降のインターネット接続が全盛の時代、2000年代後半以降のモバイル端末が一般に広がった時代、2010年代中盤以降のクラウドが一般的となった流れを振り返り、そこで稼働していたシステム(アプリケーション)の種類を考えると、システムの利用者や用途も大きく変わっていったのだろうということは想像に難くありません。

システムの調達にかかるコストや手間は、この10年でも大きく削減されました。

昔はWebアプリケーションを一般に公開するために、物理サーバを調達し、外部・内部のネットワーク環境を整備し、アプリケーションを実装して、デプロイ・テストして、ドメイン登録や切り替えをして というような煩雑な作業が多くあったはずです。

しかし現在では、AWS や GCP、 Azure などを使用すれば、IaCのコード流し込みで、そこそこ大きめのシステムでも早ければ1時間以内にアプリケーションのシステム基盤構築が完了し、簡単にアプリケーションを公開出来るようになりました。

何が言いたいかというと、アプリケーションが稼働する環境を用意するのが面倒だった時代は、1つのプラットフォームで複数の環境を賄うこともあり、例えば Apache の VirtualHost を使用して、1台のサーバが複数環境のアプリケーションを抱えており、DBサーバへの接続や関連ミドルとの接続も、各アプリケーションごとにハードコードされる時代もあったのだろうな、ということです。

また、仮想化の仕組みが整備されておらず、ローカルPCで開発したアプリケーションを ローカルPCでそのまま実行確認できなかった場合には、共有の開発サーバにアプリケーションをデプロイして確認する訳ですが、インフラやミドルの自動構成スクリプトが無い時代は、それこそデプロイやパラメータ設定などもかなりの労力があったのだろうなと思います。

近年の Mobile / Web Application アーキテクチャ

2019年09月現在の一般的な Mobile と Web のアプリケーション・アーキテクチャは次のような構成が一般的かと思います。

Webアプリケーションの部分は IaaS 、 PaaS 、 FaaS のどれを使用するかや、 Managed Service の使用有無によって細かな表現の差があるかもしれませんが、基礎的な構成を考えると、外部からの http(s) Request を受けつける Webサーバが存在し、そこから処理要求に応じて処理を実行する Applicationがあり、その先に 関係データベースやストレージ領域でデータを保持する役割を担う データリソースが存在するという構成は変わらないと思います。

環境の定義と用途

やっと本題です。

Deployment environment

Wikipedia の Deployment environment の項目には、ソフトウェアのデプロイに関して次の記述があります。

Environment/Tier Name Description
Local Developer's desktop/workstation
Development/Trunk Development server acting as a sandbox where unit testing may be performed by the developer
Integration CI build target, or for developer testing of side effects
Testing/Test/QC/
Internal Acceptance
The environment where interface testing is performed. A quality control team ensures that the new code will not have any impact on the existing functionality and tests major functionalities of the system after deploying the new code in the test environment.
Staging/Stage/Model/
Pre-production/
External-Client Acceptance/
Demo
Mirror of production environment
Production/Live Serves end-users/clients

これらをすべて使用するかは別として、システム開発プロジェクトとして使用する環境の用途の分類としては分かりやすい表現です。

この表を 乱暴に シンプルにまとめてしまうと次のようになります。

環境の用途と本番リリースまでのデプロイの流れ

それでは、定義した環境を 誰が どのように 使用するのでしょうか?

しばらく前に投稿された こちらの記事 を参考に、各環境の利用の流れをまとめてみました。

実装目線で見た環境定義と呼称

クラウド環境に各環境を構築する場合のイメージは次のようになります。

また、Webアプリケーションの実装を行う際に具体的に意識する環境名や実行環境、 git のブランチ名について考えると次のようになります。

# 環境名(英名) 環境名(和名) 場所 .env git branch
1 Production 本番環境 Remote OSに定義した
環境変数
master
2 Staging 準本番環境、デモ環境、
ステージング環境
Remote OSに定義した
環境変数
release
3 Test テスト環境 Remote OSに定義した
環境変数
develop
4 Integration システム統合環境 Remote OSに定義した
環境変数
develop
5 Development 開発環境 Local feature
6 Local ローカル環境、実装環境 Local feature

.env については、こういうものです。

なお、 git のブランチ構成については、次のような git-flow に従ったものをイメージしています。

環境を十分に用意できない場合の失敗

例えば、こういう環境ごとに用途を分類せずに、 Production と Local の2つの環境しか用意していない場合、ローカルPCでのコード修正をいきなり本番に上げることしかできず、適切なテストを実施できなかったり、ビジネスや品質の部門が適切に動作確認や機能検証を行うことができません。

大部分の訓練されたエンジニアからすれば「何を当たり前のことを...」という感覚になるかと思いますが、新規プロジェクトで顔なじみのないプロジェクトメンバーでプロジェクトを推進しなければならないときや、駆け出しエンジニアと少数でプロジェクトを進める際には、こういった基礎的なことを図や具体的な文書で定義して共通認識を描いておかないと、予算の都合で十分な環境を手配できなかったり、テストが始まる段階で手痛いバグを踏んだりしてしまいます。

これは サーバ側で実行される Webアプリケーションに限らず、iOS や Android 向けのアプリケーションについても同様です。
ビルドの方法や、WebAPI の向き先、使用する証明書やキー情報がハードコーディングされるケースが往々にしてあります。1度実装してしまえばその後はメンテナンスのみとなり、頻繁に変更がかかる部分ではないので見落とされがちになり Webの記事 にもなりづらく、サービスの運用経験や構築経験が浅い場合は想定もしない部分なのかもしれません。

おわりに

なるだけ汎用的に使えるような知識としてまとめることを意識して記事を書き始めてみましたが、いざやってみると難しかったです。思った通りまとめられた気がしません。

Wikipedia の Deployments environment の記事を参考に 6つも環境を分ける方法を記載しましたが、いつもは次の4つは最低でも構築するようにしています。

# 環境名(英名) 環境名(和名) 場所 .env git branch 備考
1 Production 本番環境 Remote OSに定義した
環境変数
master
2 Staging 準本番環境、
デモ環境、
ステージング環境
Remote OSに定義した
環境変数
release テスト環境もここ
5 Development 開発環境 Remote OSに定義した
環境変数
develop 統合環境もここ
6 Local ローカル環境、
実装環境
Local feature ここで UnitTest も
実行する

システム開発をする際、動くもの(本番環境)だけを意識してプロジェクトが進むことがありますが、「環境」という概念の基で、適切に運用・管理が必要だということが少しでも広まれば良いかな。

参照

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】Angular アプリケーションで AWS の CodeBuild を使う。

はじめに

Angular アプリケーションの CI/CD 自動化に AWS の CodePipeline を使うために CodeBuild を使ったので、備忘録として書きます。

前提

文章量を少なくするため、Angular のアプリケーションを既に作成し、GitHub に push しているものとします。

流れ

以下の流れで作業を行います。
S3 のバケットの作成 -> buildspec.ymlの作成 -> CodeBuild のプロジェクトの作成 -> ビルド実行

S3 のバケットの作成

今回は、ビルドの成果物をS3に保存します。そこで、まず S3 でバケットを作成します。
 S3 の管理画面を開き、「バケットを作成する」 をクリックします。
すると、バケット作成の画面が表示されるので、
バケット名、リージョンを入力して、「作成」 をクリックします。この時、バケット名は全世界で一意でなくてはなりません。これでバケットが作成されます。

buildspec.ymlの作成

次に、Angular プロジェクトのルートディレクトリに buildspec.yml という名前の yml ファイルを作成します。CodeBuild を実行する時はこのファイルにしたがってビルドが実行されます。

version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 10
    commands:
      - npm install -g
      - npm install --save-dev @angular-devkit/build-angular
      - npm install -g @angular/cli
  build:
    commands:
      - ng build
artifacts:
  files:
    - '**/*'
  base-directory: 'dist*'

「runtime-versions」 に使用する nodejs の version を記入し、「commands」 に インストールの command やビルド時の command を記入します。artifacts にはビルドの成果物の path を記入します。base-directoryが成果物のディレクトリで、filesが成果物のファイルです。ng new コマンドで Angular プロジェクトを作成した時のデフォルトでは、ng build でビルドすると、dist 下に成果物が作成されるので、base-directory に dist* を、files に */ を記入します。
 詳しい buildspec.yml の仕様は AWS 提供の公式ガイドがありますのでそちらをご覧ください。
buildspec.yml を書き終えたら、commit して GitHub に push します。

CodeBuild のプロジェクトの作成

CodeBuild のプロジェクトを作成します。
 CodeBuild のページの右上の 「ビルドプロジェクトを作成する」 ボタンをクリックします。すると、「ビルドプロジェクトを作成する」 画面に遷移するので、プロジェクト名に適当なものを入力し、ソースプロバイダにGitHub を選択します。
「リポジトリ」 は public か、private のどちらかを選択して、リポジトリの URL を記入します。GitHub の認証画面に飛ぶので GitHub の ID とパスワードを入力し、認証します。

環境欄は、マネージド型イメージにチェックを付け、オペレーティングシステムに ubuntu を選択します。ランタイムには Standard を選択します。イメージとイメージのバージョンは最新のものを選択します。
 Buildspec 欄は、「buildspec ファイルを使用する」 にチェックを付けます。
 アーティファクト欄には、ビルド成果物の格納先を記入します。今回は先ほど作成した S3 のバケットを使用するので、タイプを S3 にします。バケット名の欄にフォーカスすると自分のバケット名の一覧が表示されるので、先ほど作成したバケット名を選択します。名前は、今回はzip形式にするので、拡張子が .zip のファイル名にして、アーティファクトのパッケージ化で 「zip」 にチェックを入れます。

ログの欄は 「CloudWatch Logs - オプショナル」 のチェックがオンになっていることを確認し、「ビルドプロジェクトを作成する」 をクリックします。

ビルド実行

ビルドを実行するには、ビルドプロジェクトのページ右上の 「ビルドの開始」 ボタンをクリックします。ビルドステータスが 「成功」 と表示されていれば、ビルド成功です。もし、成功しなかった場合は、画面下の 「ビルドログ」 欄にログが表示されているので、それを見て調査します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[terraform]ECS タスク定義で前のリビジョンを消さずに新しいリビジョンを作る

タイトル長い

前提

ECSをterraform管理しようとした場合、タスク定義に以下のようなtfファイルを書きますよね

task.tf
resource "aws_ecs_task_definition" "my_web_server_task" {
  family                = "my-web-server-task"
  container_definitions = "${file(format("%s/my-web-server-task.json", path.module))}"
  network_mode = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu = "256"
  memory = "512"
  task_role_arn            = "${aws_iam_role.ecs_task_role.arn}"
  execution_role_arn       = "${aws_iam_role.ecs_task_role.arn}"
}
my-web-server-task.json
[
  {
    "name": "nginx",
    "image": "nginx:1.16.1",
    "essential": true,
    "portMappings": [
      {
        "containerPort": 8080,
        "hostPort": 8080
      }
    ]
  }
]

問題

上記tfファイルの定義の場合, my-web-server-task.json などに変更があった場合、 terraform apply コマンドの動作としてはreplaceになります

+++ b/terraform/ecs/my-web-server-task.json
@@ -1,7 +1,7 @@
 [
   {
     "name": "nginx",
-    "image": "nginx:1.16.1",
+    "image": "nginx:1.17.3",
     "essential": true,
     "portMappings": [
       {
$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # module.m_ecs.aws_ecs_task_definition.my_web_server_task must be replaced
-/+ resource "aws_ecs_task_definition" "my_web_server_task" {
      ~ arn                      = "arn:aws:ecs:ap-northeast-1:<user>:task-definition/my-web-server-task:1" -> (known after apply)
      ~ container_definitions    = jsonencode(
          ~ [ # forces replacement
              ~ {
                  - cpu          = 0 -> null
                  - environment  = [] -> null
                    essential    = true
                  ~ image        = "nginx:1.16.1" -> "nginx:1.17.3"
~()~

「古いrevisionが削除され、新しいrevisionが作成される」という感じですね
せっかくaws側でrevision管理されているのだから過去分は残して欲しいところ

対策

count パラメタで新しいリソースを追加する形にしてあげれば、旧revisionを残したまま新しいrevisionを作成することができます

task.tf
locals {
  revision = 1
}

resource "aws_ecs_task_definition" "my_web_server_task" {
  family                = "my-web-server-task"
  count = "${local.revision}"
  container_definitions = "${file(format("%s/my-web-server-task.json-%d", path.module, count.index+1))}"
  network_mode = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu = "256"
  memory = "512"
  task_role_arn            = "${aws_iam_role.ecs_task_role.arn}"
  execution_role_arn       = "${aws_iam_role.ecs_task_role.arn}"
}

jsonファイル名にはrevisionが入ります

my-web-server-task-1.json
[
  {
    "name": "nginx",
    "image": "nginx:1.16.1",
    "essential": true,
    "portMappings": [
      {
        "containerPort": 8080,
        "hostPort": 8080
      }
    ]
  }
]

更新する場合は新たなrevisionに対応するjsonファイルを用意し、countの数値をひとつ増やします

diff --git a/terraform/ecs/task.tf b/terraform/ecs/task.tf
index 5b43885..c494b15 100644
--- a/terraform/ecs/task.tf
+++ b/terraform/ecs/task.tf
@@ -15,7 +15,7 @@
 # }

 locals {
-  revision = 1
+  revision = 2
 }
my-web-server-task-2.json
[
  {
    "name": "nginx",
    "image": "nginx:1.17.3",
    "essential": true,
    "portMappings": [
      {
        "containerPort": 8080,
        "hostPort": 8080
      }
    ]
  }
]
$ terraform/local  terraform apply                                                                                                                                                                          

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

createのみになりましたね

欠点

ただし、この方法を使用する場合、一度に複数revisionを作ろうとした場合に

Error: ClientException: Too many concurrent attempts to create a new revision of the specified family.

が出て作成できません、revisionがsequentialな番号を必要とするのに対してterraform側が並列に作りに行くのが理由ですね
仮に複数段作成の対応をするならrevisionをインクリメントしつつ terraform apply を実行してくれる外部ツールなどが必要ですね

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amplifyを使ってReact Native + Cognitoでログイン機能を爆速で作成する

プロジェクトのテンプレを作成

最近react-native-cliが更新されて、npxで利用するのが推奨されているようです。

# 既にreact-native-cliをグローバルインストールしてる場合は削除
npm uninstall -g react-native-cli
npx react-native init MyApp --template react-native-template-typescript
cd MyApp

npmの依存モジュールを追加

npm i -S aws-amplify aws-amplify-react-native amazon-cognito-identity-js react-native-vector-icons
npx react-native link amazon-cognito-identity-js
npx react-native link react-native-vector-icons
(cd ios && pod update && pod install)

amplifyの設定

npx amplify init
npx amplify add api
# 認証はCognito、種類はGraphQL、それ以外はデフォルトで
npx amplify push

ログイン画面の作成

App.tsx
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * Generated with the TypeScript template
 * https://github.com/react-native-community/react-native-template-typescript
 *
 * @format
 */

import React, {Fragment} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  ScrollView,
  View,
  Text,
  StatusBar,
} from 'react-native';

import {
  Header,
  LearnMoreLinks,
  Colors,
  DebugInstructions,
  ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';

import Amplify from 'aws-amplify';
import awsconfig from './aws-exports';
import { withAuthenticator } from 'aws-amplify-react-native';

Amplify.configure(awsconfig);

const App = () => {
  return (
    <Fragment>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView>
        <ScrollView
          contentInsetAdjustmentBehavior="automatic"
          style={styles.scrollView}>
          <Header />
          {global.HermesInternal == null ? null : (
            <View style={styles.engine}>
              <Text style={styles.footer}>Engine: Hermes</Text>
            </View>
          )}
          <View style={styles.body}>
            <View style={styles.sectionContainer}>
              <Text style={styles.sectionTitle}>Step One</Text>
              <Text style={styles.sectionDescription}>
                Edit <Text style={styles.highlight}>App.tsx</Text> to change this
                screen and then come back to see your edits.
              </Text>
            </View>
            <View style={styles.sectionContainer}>
              <Text style={styles.sectionTitle}>See Your Changes</Text>
              <Text style={styles.sectionDescription}>
                <ReloadInstructions />
              </Text>
            </View>
            <View style={styles.sectionContainer}>
              <Text style={styles.sectionTitle}>Debug</Text>
              <Text style={styles.sectionDescription}>
                <DebugInstructions />
              </Text>
            </View>
            <View style={styles.sectionContainer}>
              <Text style={styles.sectionTitle}>Learn More</Text>
              <Text style={styles.sectionDescription}>
                Read the docs to discover what to do next:
              </Text>
            </View>
            <LearnMoreLinks />
          </View>
        </ScrollView>
      </SafeAreaView>
    </Fragment>
  );
};

const styles = StyleSheet.create({
  scrollView: {
    backgroundColor: Colors.lighter,
  },
  engine: {
    position: 'absolute',
    right: 0,
  },
  body: {
    backgroundColor: Colors.white,
  },
  sectionContainer: {
    marginTop: 32,
    paddingHorizontal: 24,
  },
  sectionTitle: {
    fontSize: 24,
    fontWeight: '600',
    color: Colors.black,
  },
  sectionDescription: {
    marginTop: 8,
    fontSize: 18,
    fontWeight: '400',
    color: Colors.dark,
  },
  highlight: {
    fontWeight: '700',
  },
  footer: {
    color: Colors.dark,
    fontSize: 12,
    fontWeight: '600',
    padding: 4,
    paddingRight: 12,
    textAlign: 'right',
  },
});

export default withAuthenticator(App, true)

npx react-native run-ios

error: Cycle insideのエラーが出た場合は、
ios/MyApp.xcworkspaceを開いて、File>Workspace Settings...のBuild System:をLegacy Build Systemに変更する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Node.js】AWS SSMパラメータストアの値を取得する

関連

使用するモジュール

  • aws-sdk
    公式で提供されているライブラリ
npm i --save-dev aws-sdk

サンプルコード

今回はAWS SSM パラメータストアに以下のようなパラメータが作成してある。

パラメータ名
sample-parameter HOGEHOGE!!
const AWS = require('aws-sdk');

const main = async () => {
    // 認証情報
    // `~/.aws/credentials`に認証情報が設定されている場合、
    // または環境変数`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`に認証情報が設定されている場合は
    // `access_key_id`と`secret_access_key`は不要
    const credentials = {
        accessKeyId: 'xxxxxxxxxxxxxxxxxxxx',
        secretAccessKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
        region: 'ap-northeast-1'
    };

    // SSMクライアントを作成
    const ssm = new AWS.SSM(credentials);

    // リクエスト
    const request = {
        Name: 'sample-parameter', // パラメータ名
        WithDecryption: true      // 暗号化されている場合は復号し、暗号化されていない場合は何もしない
    };
    const response = await ssm.getParameter(request).promise();

    console.log(response);
    // => { Parameter:
    //      { Name: 'sample-parameter',
    //        Type: 'SecureString',
    //        Value: 'HOGEHOGE!!',
    //        Version: 1,
    //        LastModifiedDate: 2019-09-12T15:28:02.134Z,
    //        ARN:
    //         'arn:aws:ssm:ap-northeast-1:xxxxxxxxxxxx:parameter/sample-parameter' } }

    console.log(response.Parameter.Value);
    // => HOGEHOGE!!
};
main();

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】AWS SSMパラメータストアの値を取得する

関連

準備

aws-sdk-ssmをインストールする。

下記コマンドを実行するか、Gemfileに追記する。

aws-sdkをインストールしてもいいが、結構サイズが大きいので基本的には必要なものだけインストールするようにした方がいいと思う。

gem install aws-sdk-ssm

or

gem 'aws-sdk-ssm'

サンプルコード

今回はAWS SSM パラメータストアに以下のようなパラメータが作成してある。

パラメータ名
sample-parameter HOGEHOGE!!
require 'aws-sdk-ssm'

# 認証情報
# `~/.aws/credentials`に認証情報が設定されている場合、
# または環境変数`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`に認証情報が設定されている場合は
# `access_key_id`と`secret_access_key`は不要
credentials = {
  access_key_id: 'xxxxxxxxxxxxxxxxxxxx',
  secret_access_key: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  region: 'ap-northeast-1'
}

# SSMクライアントを生成
ssm_client = Aws::SSM::Client.new(credentials)

# リクエスト
request = {
  name: 'sample-parameter', # パラメータ名
  with_decryption: true     # 暗号化されている場合は復号し、暗号化されていない場合は何もしない
}
response = ssm_client.get_parameter(request)

pp response
# => #<struct Aws::SSM::Types::GetParameterResult
#     parameter=
#      #<struct Aws::SSM::Types::Parameter
#       name="sample-parameter",
#       type="SecureString",
#       value="HOGEHOGE!!",
#       version=1,
#       selector=nil,
#       source_result=nil,
#       last_modified_date=2019-09-13 00:28:02 +0900,
#       arn="arn:aws:ssm:ap-northeast-1:xxxxxxxxxxxx:parameter/sample-parameter">>

puts response.parameter.value
# => HOGEHOGE!!

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超初心者によるAWS EC2 Webサーバー構築メモ

こんにちは。ideagearの林です。
今回は、AWS EC2でWebサーバーを構築するというミッションです。
が、単純に構築の方法は先人の方々によりとてもわかりやすくまとめられているのでこの記事には記しません。

この記事はちょっとタイトルでも専門用語が多くて狼狽えるという初心者による、初心者のための用語まとめと個人的に詰まったところの補足メモです。

前提と環境

この記事は
123
これら3つの記事を参考にしています。

PuTTYを使います。OSはWindowsです。

用語集

AWS、EC2

AWS : Amazon Web Servicesのこと。
EC2(Amazon Elastic Compute Cloud)はAWSの機能の一つ。
クラウド上でサーバの作成、破棄、追加などが簡単に行えるサービス。

インスタンス

EC2から立てられたサーバの一つを指すケースが多い。(必ずしもそうではない)

S3

S3 : Simple Storage Serviceのことで文字通りストレージサービス。
クラウド上にデータを保存できる。

SSHクライアント

SSHで接続「する方」のコンピュータや、接続するときに使うプログラムのこと。

SSH

ネットワークを経由して他のコンピュータ(主にサーバ)に接続し、遠隔操作するための仕組み。やりとりする情報は全て暗号化してくれる。

補足

とりあえず何も考えず123を参考に進めていたところ少々詰まってしまったところについて、解決方法をまとめます。

インスタンス作成後、SSHでアクセス

全然読みもせず3つのサイトの通り実行していると、アクセスの仕方がわからないという沼にはまりました。他のターミナルで実行すればできましたが、PuTTYでアクセスする方法を書きます。
1.秘密鍵の形式を変える
PuTTYでは専用の鍵を使用するそうなので、ダウンロードした鍵を変換する必要があるようです。

1.1 "PuTTYgen"を開く(Windowsキー+rやProgram FilesのPuTTY内から探す)

1.2 下部の"Type of key to generate"で"RSA"を選ぶ

1.3 "Load"を押し、先にダウンロードした****.pemファイルを開く(デフォルトでは.ppkファイルのみになっており、All filesに変更しないと表示されないので注意)

1.4 "Save private key"を押し、"はい(Yes)"を押す。ファイル名は拡張子以外元のカギと同じ名前にして、任意の場所に保存する。

2.鍵を選択して接続
変換して作った鍵を使用し、SSHでアクセスします。

2.1 "PuTTY"を開く

2.2 左側で"Connection"の下の"SSH"の下にある"Auth"を選択し、右に出てくる"Browse"を押す

2.3 作成した鍵のファイル(.ppk)を開く 2.4 左側、一番上の"Session"を選択

2.5 右側の"Host Name"のところに、AWSのページに載っているアドレスをコピペします(画像の青選択の部分を参照 ※@の後には数字とドットが続きます)

(2.5.5) この設定を毎回やりたくない(これ以降もアクセスする)方は、"Saved Sessions"に任意で名前を入力し、"Save"を押して設定を登録する

2.6 "Open"を押して開く

うまくアクセスできましたか?成功したら、上記3サイトと同じ画面が表示されるはずです。PuTTYはコマンドではなく最初から接続するので、少し方法が違うようです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

S3に保存した画像を表示する(Rails)

概要

例えば、あるサービスで画像をアップロードする際にS3を使うことはよくあるだろう。
そのS3にアップロードした画像を表示させたいということは結構あるのでは?(私がそれをやりたい)
私がやりたいのは以下の条件である。

  • S3はパブリックアクセスをオフ
  • 通常S3にアクセスできるのは特定のプログラムからのみ

この条件で、サービスからのみ画像を表示するようにする方法を書きます。

実行環境

Ruby 2.6.3
AWS S3

手順

S3について

前述した通り、パブリックアクセスはオフにしておきます。

ポリシーはこれぐらいしか書いてません。

S3policy
{
    "Version": "2012-10-17",
    "Id": "Policyxxxxxxxxxxx",
    "Statement": [
        {
            "Sid": "Stmtxxxxxxxxxxx",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::xxxxxxxxxxxx:user/your_name"
            },
            "Action": [
                "s3:DeleteObject",
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::your_bucket_name/*"
        }
    ]
}

S3の特定のオブジェクトにアクセスしようとすると以下の画面のようにAccessDeniedになります。

これを回避するために一時的に閲覧可能となる期限付きURLというものがあります。

期限付きURLについて

期限付きURLとは、文字通り一時的に閲覧可能となるURLで、指定した時間を経過すると閲覧できなくなるものです。
これを利用して、サービス利用者のみS3に保存された画像を閲覧できるようにします。

ここではRubyでのやり方を示しますが、他の言語でもAWSのSDKがあればできるはずです。

AWS S3 SDKのインストール

Gemfileを使った方法を書きますが、使わない方はgemでインストールしてください。
Gemfileに以下を書いてインストールします。

Gemfile
gem 'aws-sdk-s3'

期限付きURLの発行

以下の例ではプログラムからのみアクセスできるようにS3を設定しているのでCredentialを指定しています。
表示時間は60秒として設定しました。

s3 = Aws::S3::Resource.new(
      region: REGION_NAME, # 1. 利用しているリージョン
      credentials: Aws::Credentials.new(
        AWS_ACCESS_KEY, # 2. プログラムからアクセスできるユーザのアクセスキー
        AWS_SECRET_ACCESS_KEY # 3. プログラムからアクセスできるユーザのシークレットキー
      )
    )
signer = Aws::S3::Presigner.new(client: s3.client)
presigned_url = signer.presigned_url(:get_object,
        bucket: bucket_name, key: key, expires_in: 60)

Presigner.presigned_urlで期限付きURLを発行しています。
生成されたURLにアクセスすると閲覧できるはずです。

HTMLで画面に表示する場合には通常通りimgタグのsrcにこの期限付きURLを入れれば良いだけです。

以上 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSでセキュア(HTTPS)な独自ドメインサイトを構築する手順

概要

AWSでセキュア(HTTPS)な独自ドメインサイトを構築する手順をまとめる。

Certificate Managerの設定

ドメインの登録 "zzzz.net" (有料)
ドメイン(example.net)登録料金=11$
証明書作成リージョン = US東部 バージニア北部

Route53の設定

レコードセットの登録
xxxx.zzzz.net CNAME yyyy.cloudfront.net
xxxx.zzzz.netの証明書に関するCNAME

CloudFrontの設定

  • General:
    Alternate Domain Names (CNAMEs) = xxxx.zzzz.net
    Custom SSL Certificate (example.com) = 証明書を指定
  • Behaviors:
    Viewer Protocol Policy = HTTP and HTTPS
    Allowed HTTP Methods = GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
    CloudFrontキャッシュ無効化 (Minimum/Maximum/Default TTL=0)
    Cache Based on Selected Request Headers = All
    Forward Cookies = All
    Query String Forwarding and Caching = cache based on all
  • Origins:
    Origin Protocol Policy = HTTP Only
    Origin Domain Name = EC2の外部公開ドメイン名 (xxxx.zzzz.netではない)
    Origin Response Timeout = 60
    HTTP Port = EC2の外部公開ポート番号

ブラウザからの接続

http://xxxx.zzzz.net
https://xxxx.zzzz.net

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSのEC2とRDSを利用してZabbix4.2を導入する

概要

AWS上でZabbix4.2を運用できるように構築します。AWS上でEC2とRDSを立ち上げ、立ち上げたEC2とRDSにZabbixをインストールします。
構成環境:EC2 2台 (OS : Amazon Linux)、RDS 1台(DB : MySQL)

0. 前提条件

  • TeratermなどのSSHクライアントソフトをインストールしていること
  • AWSのアカウントを作成していること
  • <推奨>セキュリティの観点でAWSのアカウントに多要素認証・IAMの設定を実施していること
    ※本書ではWindows10,Teratermを使用します。

1. VPCのセキュリティルール設定

  1. 「サービス」→「VPC」をクリックする。
  2. 左側メニューから「セキュリティグループ」をクリックする。
  3. 下側メニューから「インバウンドルール」をクリックする。
  4. 「ルールの編集」をクリックする。
  5. 下図のようにセキュリティルールを設定する。

    ※Zabbix serverからZabbix Agentに接続するには10050ポート、Zabbix agentからZabbix Serverに接続するには10051ポートを接続する必要があります。

2. EC2の立ち上げ

  1. 「サービス」→「EC2」をクリックする。
  2. 左側メニューから「インスタンス」をクリックする。
  3. 「インスタンスの作成」をクリックする。
  4. 「Amazon Linux 2 AMI (HVM), SSD Volume Type」を選択する。
  5. インスタンスタイプを選択し「次の手順:インスタンスの詳細の設定」をクリックする。

    ※本書では「t2.micro」のインスタンスタイプを使用します。
  6. 下記の項目を設定し「次の手順:ストレージの追加」をクリックする。

    • インスタンス数 : 2
    • ネットワーク : (セキュリティルールを設定したVPCを選択)
  7. サイズやボリュームタイプを選択し「次の手順:タグの追加」をクリックする。
  8. タグの設定が必要なければ「次の手順:セキュリティグループの設定」をクリックする。
  9. 「既存のセキュリティグループを選択する」を選択し「確認と作成」をクリックする。
  10. 設定した内容が正しいことを確認し「起動」をクリックする。
  11. インスタンスへのログインに必要であるキーペアの作成画面が表示される。「キーペア名」にプライベートキーの名前を入力し「キーペアのダウンロード」をクリックする。その後「インスタンスの作成」をクリックする。

    ※プライベートキーの再発行はできません。プライベートキーを紛失すると作成したEC2にログインできなくなるので、なくさないように厳重に管理してください。
  12. 「作成ステータス」画面が表示されたことを確認する。
  13. 下部の「インスタンスの表示」をクリックする。
  14. インスタンスが2台作成されたことを確認する。

3. RDSの作成

  1. 「サービス」→「RDS」をクリックする。
  2. 「データベース」をクリックする。
  3. 「データベースの作成」をクリックする。
  4. 「データベース設定」から「Standard Create」を選択する。
  5. 「エンジンのオプション」から下記の項目を選択する。

    • エンジンのタイプ : MySQL
    • バージョン: MySQL 5.7.26
  6. 「テンプレート」から使用する環境に合わせて選択する。

    ※本書では「無料利用枠」を選択します。
  7. 「設定」から下記の項目を入力する。

    • DBインスタンス識別子 : (AWSコンソール上で表示される名前)
    • マスターユーザー名 : (MySQLのログインユーザー名)
    • マスターパスワード : (MySQLのパスワード)
  8. 「DBインスタンスサイズ」からインスタンスサイズを選択する。

    ※本書では「db.t2.micro」を選択します。
  9. 「Storage」から容量を入力する。
  10. 「接続」からセキュリティルールを設定したVPCを選択する。
  11. 「データベースの作成」をクリックする。
  12. データベースが作成されている画面が表示される。
  13. データベースが正常に作成されたことを確認する。

4. Zabbix Server側の設定

(a) Teratermでログイン

  1. 作成したEC2のインスタンスを選択し「接続」をクリックする。
  2. 「スタンドアロンSSHクライント」を選択する。画面に表示されている「パブリックDNS」をコピーする。
  3. Teratermを起動し「ホスト」にコピーしたパブリックDNSをペーストする。その後「OK」をクリックする。
  4. 「セキュリティ警告」画面にて「続行」をクリックする。
  5. 「ユーザー名」をec2-user、「秘密鍵」にてダウンロードしたプライベートキーを選択し「OK」をクリックする。
  6. Amazon Linuxにログインしたことを確認する。

(b) Zabbixのインストール

  1. アップデート可能な全パッケージをアップデートする。

    $ sudo yum -y update
    
  2. 永続的にSELinuxを無効化する。

    $ sudo vi /etc/selinux/config
    SELINUX=disabled
    $ sudo reboot
    
  3. Zabbix4.2のリポジトリをインストールする。

    $ sudo rpm -Uvh https://repo.zabbix.com/zabbix/4.2/rhel/7/x86_64/zabbix-release-4.2-2.el7.noarch.rpm
    
  4. zabbix-server-mysql,zabbix-web-mysql,zabbix-web-japanese,zabbix-agentをインストールする。

    $ sudo yum -y install zabbix-server-mysql zabbix-web-mysql zabbix-web-japanese zabbix-agent
    

(c) MySQLの設定

  1. MySQLクライアントをインストールする。

    $ sudo yum -y install mysql mysql-devel
    
  2. RDSに接続する。

    $ sudo mysql -h RDSのエンドポイント -P 3306 -u ユーザ名 -p
    
  3. Zabbixのデータベースを作成する。

    > create database zabbix character set utf8 collate utf8_bin;
    
  4. Zabbixユーザを作成する。

    > grant all on zabbix.* to zabbix@`%` identified by 'パスワード';
    
  5. MySQLからログアウトする。

    > quit;
    
  6. Zabbixの初期データを登録する。

    $ sudo zcat /usr/share/doc/zabbix-server-mysql*/create.sql.gz | mysql -h RDSのエンドポイント -P 3306 -u ユーザ名 -p zabbix
    
  7. /etc/zabbix/zabbix_server.confにDB情報を登録する。

    $ sudo vi /etc/zabbix/zabbix_server.conf
    DBHost=RDSのエンドポイント
    DBPassword=RDSのパスワード
    
  8. zabbix-serverの起動・自動起動を設定する。

    $ systemctl start httpd
    $ systemctl enable httpd
    $ systemctl start zabbix-server
    $ systemctl enable zabbix-server
    

(d) Zabbixの設定

  1. ブラウザにて「http://(インスタンスのパブリックIP)/zabbix」に接続する。

  2. 「Welcome to Zabbix 4.2」と表示されたことを確認する。その後「Next step」をクリックする。
  3. 「Check of pre-requisites」にてすべての項目が「OK」と表示されていることを確認する。その後「Next step」をクリックする。
  4. 「Configure DB connection」にて下記の項目を入力し「Next step」をクリックする。

    • Database host : (RDSのエンドポイント)
    • Password : (zabbixユーザのパスワード)
  5. 「Zabbix server details」にて「Next step」をクリックする。
  6. 「Pre-installation summary」にて正しい設定値であることを確認し「Next step」をクリックする。
  7. 「Congratulations! You have successfully installed Zabbix frontend.」と表示されたことを確認し「Finish」をクリックする。
  8. Zabbixのログイン画面が表示される。下記の初期ユーザ(Admin)・初期パスワード(zabbix)を入力し「Sign in」をクリックする。
  9. Zabbixのダッシュボードが表示されたことを確認する。
  10. 「Zabbix agent on Zabbix server is unreachable for 5 minutes」の警告を消すためにzabbix-agentを起動する。

    $ sudo systemctl start zabbix-agent
    $ sudo systemctl enable zabbix-agent
    
  11. 「Zabbix agent on Zabbix server is unreachable for 5 minutes」の警告が消えたことを確認する。

(e) Zabbixの日本語化設定

  1. 右側上部の人型マークをクリックする。
  2. 「Language」を「Japanese (ja_JP)」に変更し「Update」をクリックする。
  3. 日本語に設定されたことを確認する。

5. Zabbix Agent側の設定

(a) Zabbix-agentのインストール

  1. 前章の設定方法を参考にして、2台目のEC2インスタンスにターミナルソフトでログインする。

  2. アップデート可能な全パッケージをアップデートする。

    $ sudo yum -y update
    
  3. 永続的にSELinuxを無効化する。

    $ sudo vi /etc/selinux/config
    SELINUX=disabled
    $ sudo reboot
    
  4. Zabbix4.2のリポジトリをインストールする。

    $ sudo rpm -Uvh https://repo.zabbix.com/zabbix/4.2/rhel/7/x86_64/zabbix-release-4.2-2.el7.noarch.rpm
    
  5. zabbix-agentをインストールする。

    $ sudo yum install -y zabbix-agent
    
  6. /etc/zabbix/zabbix_agentd.confにZabbix Server側のIPアドレスを入力する。

    $ sudo vi /etc/zabbix/zabbix_agentd.conf
    Server=(Zabbix Server側のIPアドレス)
    ServerActive=(Zabbix Server側のIPアドレス)
    
  7. zabbix-agentの起動・自動起動を設定する。

    $ sudo systemctl start zabbix-agent
    $ sudo systemctl enable zabbix-agent
    

(b) Zabbix(GUI)でZabbix Agentの登録

  1. ダッシュボードから「設定」をクリックする。
  2. 「ホスト」をクリックする。
  3. 「ホストの作成」をクリックする。
  4. 下記の項目を入力し「テンプレート」をクリックする。

    • ホスト名 : (分かりやすい名前を入力)
    • 表示名 : (分かりやすい名前を入力)
    • グループ : Linux servers
    • IPアドレス : (Zabbix Agent側のIPアドレスを入力)
  5. 「テンプレートのリンク」に「Template OS Linux」を設定し「追加」をクリックする。
  6. 「ホストを追加しました」と表示されたことを確認する。
  7. Zabbix Agent側の情報が取得できていることを確認する。
  8. <障害試験>Zabbix Agent側にてzabbix-agentを停止する。その後、Zabbixのダッシュボード画面にてZabbix Agent側と疎通できない警告が表示されたことを確認する。

最後に

AWSのEC2、RDSを利用してZabbix4.2を運用することができます。これによりクラウド上でインスタンスの死活監視ができます。

※実際に運用する際はt2.microではメモリが枯渇しますので、大容量のインスタンスタイプを選択して運用してください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon SageMaker で画像からオブジェクト(道路標識)を検出する

Amazon SageMaker は機械学習のワークフロー全体をカバーする AWS の完全マネージド型サービスであるが、実際に何ができてどのように使えばいいのか分からなかったため、今回は Amazon SageMaker の オブジェクト検出アルゴリズム を使用して、画像から下図のような日本の道路に広く見られる道路標識(方面及び方向の予告)を検出してみた。

教師データの作成

映像からスクリーンショットを抽出

まずは、学習用に道路標識を含む大量の画像を用意する必要がある。そこで、走行中の車内から前方を撮影した映像を用意し、FFmpeg により 4 秒おきにスクリーンショットを取得することで、大量の画像を得た(以下のコマンドでは yadif フィルターによるインターレース解除処理を同時に行っている)。

ffmpeg -loglevel warning -i input.mp4 -vf yadif=0:-1:1 -r 0.25 output_%04d.jpg

画像のアノテーション(タグ付け)

抽出した大量の画像をもとに教師データを作成する。すなわち、画像内の道路標識の部分を手作業で指定してやる必要がある。Amazon SageMaker Ground Truth というサービスを利用して、この作業を外部のパブリックチームや社内のプライベートチームなどに委託することもできるが、今回は小規模のため自力で行うこととする。

このような、画像のタグ付け(アノテーション)を少しでもラクにするためにさまざまなツールが存在しているが、今回は Microsoft が開発している VoTT (Visual Object Tagging Tool) を使用した。VoTT によるアノテーションについては こちら の別記事を参照されたい。

RecordIO 形式のファイルを作成

Amazon SageMaker のオブジェクト検出アルゴリズムでは入力ファイル形式として Apache MXNet RecordIO 形式が推奨されているため、VoTT により作成したアノテーションデータ(JSON 形式)を RecordIO 形式に変換する必要がある。変換の手順については こちら の別記事を参照されたい。

Amazon S3 に教師データをアップロード

RecordIO 形式のファイルを下記の通り Amazon S3 にアップロードした。なお、機械学習においては一般的に、過学習を防ぐ目的で、学習用データと検定用データを別々に用意することが多い。今回は、タグ付けした 240 枚の画像から無作為に抽出した 160 枚の画像を学習用データ、残りの 80 枚を検定用データとして、それぞれ RecordIO 形式のファイルを作成した。

S3 URI 説明
s3://example-bucket/train/train.rec 学習用データ (RecordIO 形式)
s3://example-bucket/validation/validation.rec 検定用データ (RecordIO 形式)

ノートブックインスタンスの作成

機械学習を始めるにあたり、データの前処理や可視化、アルゴリズムの検討などを行うために Jupyter Notebook というツールを使う。これはブラウザ上で Python のコードを対話的に実行できるようなもので、機械学習やデータ分析の分野では一般的に使われている。

通常は Jupyter Notebook をローカル PC にインストールしたり Docker コンテナとして入手して使用したりするが、Amazon SageMaker ノートブックインスタンスは、いわば AWS のマネージド Jupyter Notebook のようなもので、インスタンスを作成するとすぐに Jupyter Notebook が使用できるようになる。

ノートブックインスタンスは Amazon SageMaker マネジメントコンソールから簡単に作成できる。
参考: https://docs.aws.amazon.com/ja_jp/sagemaker/latest/dg/gs-setup-working-env.html

トレーニングジョブの作成

ここからはノートブックインスタンスで稼働する Jupyter Notebook 上で Python コードを実行していく。Jupyter Notebook の使い方についてここでは説明しないが、基本的には「セル」と呼ばれるエリアにコードを記述し、Shift + Enter キーを押してコードを実行するという作業の繰り返しになる。

以下では https://github.com/awslabs/amazon-sagemaker-examples/blob/master/introduction_to_amazon_algorithms/object_detection_pascalvoc_coco/object_detection_recordio_format.ipynb を参考にトレーニングジョブの作成を行う。

import sagemaker
from sagemaker import get_execution_role

role = get_execution_role()
print(role)
sess = sagemaker.Session()

Amazon SageMaker の組み込みアルゴリズムの中から「オブジェクト検出アルゴリズム」の最新のイメージを読み込む。

from sagemaker.amazon.amazon_estimator import get_image_uri

training_image = get_image_uri(sess.boto_region_name, 'object-detection', repo_version='latest')
print(training_image)

トレーニングを行うインスタンスを設定する。第 1 引数にアルゴリズムのイメージ、第 2 引数に IAM ロールを指定する。train_instance_count はインスタンスの台数、train_instance_type はインスタンスタイプを表す。なお、オブジェクト検出アルゴリズムでは GPU を搭載した P2 または P3 インスタンスファミリーを選択する必要があることに注意。また、GPU インスタンスは高額なので使いすぎには十分注意したい。train_volume_size はインスタンスに与える追加のボリュームサイズ(GB 単位)。train_max_run はトレーニングの最大実行時間(秒)で、最初は短めに設定しておくことを推奨する。output_path にはトレーニング済みのモデルを出力する Amazon S3 の URI を指定する。
参考: https://sagemaker.readthedocs.io/en/stable/estimators.html

od_model = sagemaker.estimator.Estimator(training_image,
                                         role,
                                         train_instance_count=1,
                                         train_instance_type='ml.p2.xlarge',
                                         train_volume_size=8,
                                         train_max_run=600,
                                         input_mode='File',
                                         output_path='s3://example-bucket/sagemaker/output',
                                         sagemaker_session=sess)

続いてハイパーパラメータと呼ばれるトレーニングに関連する各種パラメータを調整する。num_classes には教師データに何種類のタグが含まれるかを指定する。num_training_samples は教師データに含まれる画像の枚数を指定する。トレーニング中にメモリ不足が発生した場合は mini_batch_size を小さくする(ml.p2.xlarge インスタンスで mini_batch_size = 32 の場合、メモリ不足が発生した)。その他の項目は基本的にデフォルト値のままとしている。
参考: https://docs.aws.amazon.com/ja_jp/sagemaker/latest/dg/object-detection-api-config.html

od_model.set_hyperparameters(base_network='vgg-16',
                             use_pretrained_model=1,
                             num_classes=4,
                             mini_batch_size=8,
                             epochs=30,
                             learning_rate=0.001,
                             lr_scheduler_factor=0.1,
                             optimizer='sgd',
                             momentum=0.9,
                             weight_decay=0.0005,
                             overlap_threshold=0.5,
                             nms_threshold=0.45,
                             image_shape=512,
                             label_width=350,
                             num_training_samples=240)

Amazon S3 にアップロードした教師データ(学習用と検定用)の情報を指定する。第 1 引数の S3 URI は RecordIO 形式のファイルへのフルパス、または同ファイルが格納されているディレクトリ名を指定する。ディレクトリ名を指定する場合は、ディレクトリ内に RecordIO 形式のファイルが複数存在してはならないことに注意。

train_data = sagemaker.session.s3_input('s3://example-bucket/train',
                                        distribution='FullyReplicated',
                                        content_type='application/x-recordio',
                                        s3_data_type='S3Prefix')

validation_data = sagemaker.session.s3_input('s3://example-bucket/validation',
                                             distribution='FullyReplicated',
                                             content_type='application/x-recordio',
                                             s3_data_type='S3Prefix')

data_channels = {'train': train_data, 'validation': validation_data}

次のコードを実行すると、トレーニングジョブが開始される。トレーニングが終了するか、train_max_run で指定した最大実行時間が経過すると、Amazon S3 の指定した場所にモデルが .tar.gz 形式で出力される。

od_model.fit(inputs=data_channels, logs=True)

なお、トレーニングインスタンスの料金については、実際にトレーニングを行っていた時間に対してのみ秒単位で課金される。すなわち、トレーニングインスタンスが起動しても、教師データの不備や設定ミスによって実際にトレーニングが始まる前にジョブが終了した場合は、課金されないということになる。

エンドポイントの作成

トレーニングにより出力されたモデルを使用して実際に未知データの推論を行うため、エンドポイントを作成する。エンドポイントのインスタンスは、トレーニングインスタンスとは異なるインスタンスタイプを選択することができる。

object_detector = od_model.deploy(initial_instance_count=1, instance_type='ml.m5.large')

未知データの推論

Jupyter Notebook に画像ファイルをアップロードし、作成したエンドポイントで道路標識の検出を試みた。

import json

file_name = 'test01.jpg'
with open(file_name, 'rb') as image:
    f = image.read()
    b = bytearray(f)
    ne = open('n.txt', 'wb')
    ne.write(b)

object_detector.content_type = 'image/jpeg'
results = object_detector.predict(b)
detections = json.loads(results)
print(detections)

検出結果を画像に重ね合わせて可視化するための関数 visualize_detection を用意する。

def visualize_detection(img_file, dets, classes=[], thresh=0.6):
        """
        visualize detections in one image
        Parameters:
        ----------
        img : numpy.array
            image, in bgr format
        dets : numpy.array
            ssd detections, numpy.array([[id, score, x1, y1, x2, y2]...])
            each row is one object
        classes : tuple or list of str
            class names
        thresh : float
            score threshold
        """
        import random
        import matplotlib.pyplot as plt
        import matplotlib.image as mpimg

        img=mpimg.imread(img_file)
        plt.imshow(img)
        height = img.shape[0]
        width = img.shape[1]
        colors = dict()
        for det in dets:
            (klass, score, x0, y0, x1, y1) = det
            if score < thresh:
                continue
            cls_id = int(klass)
            if cls_id not in colors:
                colors[cls_id] = (random.random(), random.random(), random.random())
            xmin = int(x0 * width)
            ymin = int(y0 * height)
            xmax = int(x1 * width)
            ymax = int(y1 * height)
            rect = plt.Rectangle((xmin, ymin), xmax - xmin,
                                 ymax - ymin, fill=False,
                                 edgecolor=colors[cls_id],
                                 linewidth=3.5)
            plt.gca().add_patch(rect)
            class_name = str(cls_id)
            if classes and len(classes) > cls_id:
                class_name = classes[cls_id]
            plt.gca().text(xmin, ymin - 2,
                            '{:s} {:.3f}'.format(class_name, score),
                            bbox=dict(facecolor=colors[cls_id], alpha=0.5),
                                    fontsize=12, color='white')
        plt.show()

最後に結果を出力する。visualize_detection の第 3 引数に渡す配列は、教師データをタグ付けする際に、検知したい各物体に割り当てた番号との対応関係を表すものである(今回は青色の標識を 0 , 緑色の標識を 1 , 白色の標識を 2 としてタグ付けを行なっている)。

object_categories = ['blue-sign', 'green-sign', 'white-sign']
threshold = 0.20
visualize_detection(file_name, detections['prediction'], object_categories, threshold)

実行結果

教師データの量が少なくトレーニングの時間も不十分だったため確度は低いものの、道路標識の検出には成功した。教師データを増やしたり、トレーニングの時間を十分に確保したりすることで、さらに検出精度が向上することが期待できる。



エンドポイントの削除

エンドポイントはデプロイされている時間に応じて課金されるため、不要になったら必ず削除する。

sagemaker.Session().delete_endpoint(object_detector.endpoint)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon SageMaker ノートブックインスタンスで最新版の SageMaker Python SDK を使用する

2019 年 8 月より Amazon SageMaker のトレーニングジョブでスポットインスタンスが使用できるようになったため、早速試してみようとしたところ、SageMaker ノートブックインスタンスにプリインストールされる SageMaker Python SDK のバージョンが古く使用できなかった。そこで、本稿ではこの SageMaker Python SDK のアップデート方法について記述する。

関連記事

解決方法

ライフサイクル設定 を作成し、以下のスクリプトをノートブックインスタンスの開始時 (Start notebook) に実行するよう設定する。

#!/bin/bash
set -e

sudo -u ec2-user -i <<'EOF'
source /home/ec2-user/anaconda3/bin/activate mxnet_p36
pip install --upgrade sagemaker
source /home/ec2-user/anaconda3/bin/deactivate
EOF

initctl restart jupyter-server --no-wait

mxnet_p36 の部分はノートブックインスタンスで使用するカーネルに合わせて適宜書き換える。mxnet_p36 と記述した場合は、conda_mxnet_p36 カーネルでのみ最新版の SageMaker Python SDK が使用できるようになる。

なお、以下のように記述すればすべてのカーネルで最新版の SageMaker Python SDK が使用できるようになるが、スクリプトの実行に 5 分以上かかるとノートブックインスタンスの起動に失敗することに注意が必要だ。

#!/bin/bash
set -e

sudo -u ec2-user -i <<'EOF'
for env in base /home/ec2-user/anaconda3/envs/*; do
  source /home/ec2-user/anaconda3/bin/activate $(basename "$env")
  if [ $env = 'JupyterSystemEnv' ]; then continue; fi
  pip install --upgrade sagemaker
  source /home/ec2-user/anaconda3/bin/deactivate
done
EOF

initctl restart jupyter-server --no-wait

実際にスポットトレーニングを実行してみる

Estimator のパラメータに train_use_spot_instances , train_max_wait , checkpoint_s3_uri , checkpoint_local_path を追加する。最新版の SageMaker Python SDK がインストールされていれば正常に動作する。

od_model = sagemaker.estimator.Estimator(
    image_name,
    role,
    train_instance_count=1,
    train_instance_type='ml.p3.2xlarge',
    train_volume_size=8,
    train_max_run=3600,
    input_mode='File',
    output_path='s3://example-bucket/2019-09-15/output',
    sagemaker_session=session,
    train_use_spot_instances=True,
    train_max_wait=3600,
    checkpoint_s3_uri='s3://example-bucket/2019-09-15/checkpoints',
    checkpoint_local_path='/opt/ml/checkpoints')

参照: Estimators — sagemaker 1.39.2 documentation

知見など

  • ノートブックインスタンスのターミナルから pip install --upgrade sagemaker を実行しても効果がなかった。
  • 上記を実行後ノートブックインスタンスを再起動すると SageMaker Python SDK のバージョンが元に戻っていた。

参考

GitHub の以下のリポジトリに AWS 公式のライフサイクル設定例がいくつか紹介されている。
https://github.com/aws-samples/amazon-sagemaker-notebook-instance-lifecycle-config-samples

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Linuxのkillコマンドについてまとめた

プロセスを強制終了させる際に、 kill <PID> を使用したことはあると思います。
また、以前systemdについてまとめましたが、Unitファイル内でも kill コマンドの扱いを定義していました。

今回はこの kill コマンドについて、詳しく調べてみました。

killコマンドについて

説明

kill コマンドは、指定したシグナルを指定したプロセスまたはプロセスグループへ送る。 シグナルが指定されない場合、TERMシグナルを送る。 TERMシグナルは、このシグナルをキャッチしないプロセスを終了させる。 このシグナルをキャッチしてしまうプロセスを終了させるためには、 KILL (9) シグナルを使う必要がある。
最近のシェルのほとんどには、組み込みのkill機能があり、 ここで説明しているコマンドと同じような使い方をする。 -a' オプションと-p' オプション、 そしてコマンド名で PID を指定する方法はローカルな拡張である。

出典

つまり、kill コマンドはプロセスの強制終了のためではなく、プロセスへ何らかのシグナルを送るために用意されています。
そして、シグナルに TERM が使用された場合に、プロセスを強制終了させるます。

それでは、シグナルとはどういったもので、どのような種類があるのでしょうか。

シグナル

シグナルとは

「シグナル」はプロセスとプロセスの間で通信を行う際に使用される“信号”のことで、シグナルを受け取ったプロセスは“何らかの動作”を行います。その動作は、例えば「再起動」であったり、「終了」であったりします。

出典

コンソールでkill <PID>Ctrl+cCtrl+zと入力するとプロセスが終了しますが、これらもプロセスへ終了シグナルを送っていることになります。

シグナルの種類

シグナルの種類を一部、下記の通り紹介します。

先述の例で、Ctrl+c を上げましたが、これは下記のSIGINT を該当のプロセスへ送信していることになります。

シグナルの種類 シグナル番号 デフォルト動作 備考
SIGINT 2 終了 キーボードからの割り込み (Interrupt)
SIGTSTP 20 停止 端末より入力された一時停止 (stop)
SIGKILL 9 終了 Kill シグナル
SIGTERM 15 終了 終了 (termination) シグナル
SIGHUP 1 終了 制御端末(controlling terminal)のハングアップ検出、または制御しているプロセスの死
SIGQUIT 3 終了 キーボードによる中止 (Quit)、プロセスの終了とコアダンプ出力

出典

シグナル動作の種類

シグナルが送信されると、プロセスの通常処理に割り込んでシグナルが動作します。
シグナルの種類によりデフォルト動作は定義されており、以下の動作をそれぞれ実行します。

  1. プロセスを終了する
  2. コアを出力し、プロセスを終了する
  3. シグナルを無視する
  4. 処理を一時停止する
  5. 処理を再開する

出典

シグナル挙動の上書き

デフォルト動作は上記のように定義されていますが、シグナルを受け取った際に別の処理を実行させることもできます。
この別の処理は、プログラム側で実装します。

nginxの公式サイトにも、それぞれのシグナルを受け取った際にメインプロセスがどのような振る舞いをするかが記されています。

kill コマンドの使い方

書式

$ kill [-s signal | -p] [-a] [ --] pid ...
$ kill -l [signal]  

killのあとに、シグナル名もしくはシグナル番号を指定してPIDに対してコマンドを実行します。

まとめ

  • kill コマンドは、プロセスに対してシグナルを送信する。
  • シグナルには種類があり、それぞれ動作が異なる。
  • 各シグナルにはデフォルト動作があるか、その動作をプログラム側で書き換えることも可能。

補足

Unitファイル内で KillMode オプションを指定できます。
各値の動作は以下のようになっています。

systemdは起動したサービスに関連する全てのプロセスをCgroupの個別のグループに入れて管理している

ExecStopコマンドで停止した時、グループ内にプロセスが残っている場合、KillModeの設定に応じて残プロセス処理を行う

  • KillMode=none 残プロセスは放置
  • KillMode=process メインプロセスが残っている場合、SIGTERM/SIGKILLで停止する。その他の残プロセスは放置
  • KillMode=control-group グループ内の全ての残プロセスを SIGTERM/SIGKILL で停止する
  • KillMode=mixed メインプロセスを SIGTERM/SIGKILL で停止し、続けてグループ内の全ての残プロセスを SIGKILL で停止する。

出典

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

怖くないよね?マルチステージビルド

会社の本番環境をAWSのEKSに移行することになりその際にgolangのアプリを配布する際に使用したマルチステージビルドについて備忘録を交えながら記事を書いていきます。

先に言うんですけど

すみません僕Docker自体初めてです

今まで特に自分でDockerなどで環境構築をしたことがなかったのでこれが初めてのDocker作成です。
ほとんど分かっていないのでこいつダメやなって思いながら見てください

マルチステージビルドについて
https://github.com/moby/moby/pull/31257

簡単に言うと、fromでから作られた2つの複数のFROMイメージ間のファイルを COPY --from で直接を参照できるようになったのと AS で中間イメージに名前が付けられるようになったと言うものです。

golangってクロスコンパイルなどでシングルバイナリさえあればgolangの実行環境不要ですもんね
(golangにいては割愛します。)

早速やりましょうか

基本的なファイル構成はこちら

├build(ここにbuildの成果物が格納される)
│ └ appname(実行バイナリ)
├cmd(実行ディレクトリ)
│ └ main.go
├configs(設定ファイル関連)
├bitbucket-pipelines.yml
├.env(環境変数設定ファイル)
├docker-compose.yml
├Dockerfile
├go.mod
├go.sum
├Makefile(タスクランナー実行ファイル)

Makefile

基本的には今回は go build なども含めて全てMakefile側に任せています。

makefileに関しての設定はここで書きませんがちゃんとbuild配下にバイナリが入るように以下のように設定しました

build: 

@go build -ldflags="-w -s" -o build/appname ./cmd/main.go

これでmake buildのコマンドをターミナルで実行するとbuild配下にappnameと言うシングルバイナリが作成されます

Dockerfile

FROM golang:1.12.9 as builder

ADD . /appname/

WORKDIR /appname/

ENV GO111MODULE=on

COPY go.mod .
COPY go.sum .

RUN go mod download
COPY . .

RUN  CGO_ENABLED=0 GOOS=linux GOARCH=amd64 make build 

FROM alpine

RUN apk update \
  && apk add --no-cache

COPY --from=builder /appname/build /app/build

ENTRYPOINT [ "/app/build/appname" ]

まずポイントを解説すると

FROM golang:1.12.9 as builder

これでgolangのbaseimageを作成しますこの as builderと言うのがこの後にこのimageを渡すためのエイリアスになります

ENV GO111MODULE=on

COPY go.mod .
COPY go.sum .

RUN go mod download
COPY . .

go.modはgolangのモジュール関連の設定ファイルです(jsのpackage.jsonみたいなもの)
https://qiita.com/propella/items/e49bccc88f3cc2407745

ENV GO111MODULE=onはおまじないみたいなもので現行のgo1.13の環境では必要ないと思う(動かなかったらいれてください)

RUN  CGO_ENABLED=0 GOOS=linux GOARCH=amd64 make build 

ここでgoのbuildを行います make buildコマンドは先ほど作成した Makefileのトリガーとして buildを設定していたのでこのようなコマンドでタスクの実行が可能です

クロスコンパイルについてはこちらを参照してください
https://qiita.com/xshirade/items/abeb0d595be27cb6326e

FROM alpine

こちらはDockerの公式が提供している軽量なLinuxのイメージです。
https://alpinelinux.org/

COPY --from=builder /appname/build /app/build

ここで先ほどbuildした実行バイナリの格納ファイル等をこちらにCOPYしています
これでマルチステージビルドの完成です

補足

これでducker build などでもいいのですが開発環境等でmysqlに接続できる等などの確認が必要だと思うのでdokcer-compose.ymlで環境の構築を行います

docker-compose.yml
version: "3"

services:
  mysql:
    image: mysql:5.7.26
    container_name: db
    env_file:
      - .env
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql
      - ./mysql/scripts/init:/docker-entrypoint-initdb.d
      - ./mysql/config:/etc/mysql/conf.d
  api:
    build: .
    depends_on:
      - mysql
    env_file:
      - .env
    ports:
      - "8080:8080"

volumes:
  db_data:

これで

$ docker-composer build && docker-compose up -d

こちらで接続ができているかなどの確認(go側にmysqlに接続などのプログラムは書いてくださいね)

最後に

僕自身初めてのDockerだったので探り探りでの実装だったのですがもし他にこれの方がいいよってものがありましたら是非教えて頂けますと幸いです。
最後まで読んで頂いてありがとうございました。

次にbitbucket-pipelines.ymlを使用したECRへのPUSHをしたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む