Lambda とEventBridgeで起動中のEC2とRDSをTeamsに通知してみた
普段から検証用に EC2 や RDS を頻繁に作成するのですが、検証後にそのまま放置してしまい、月末に注意を受けることが多々あります。
まぁ起動中なのかどうかは、コンソールをみれば一発でわかりますが、実際はそれすらも忘れてやれてませんでした。
なので、今回は EC2・RDS の起動中のリソースをチェックして、Teams に通知するものを作ってみました。
構成
使用している AWS サービスは Lambda と EventBridge だけです。
内容は Lambda で EC2 と RDS のステータスを確認し、 EventBridge で1日1回実行するようにしました。
最終的に確認結果は Teams にLambda から連携するような形です。
Teams でWebhook を作成
通知したいチャネルの・・・をクリックしてコネクタを選択します。
構成をクリックします。
Webhook 名やアイコンを設定後、以下のようにURL が表示されますので、コピーして保持しておきます。
これでTeams の設定は完了です。続いて Lambda でチェック処理を設定します。
Lambda でチェック処理
最初に今回使用したコードは以下になります。
import boto3 import pprint import urllib3 import json import logging from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context): http = urllib3.PoolManager() teams="https://xxxxxx/xxxx/xxxx/" ############ EC2 ############## client = boto3.client('ec2') responce = client.describe_instances(Filters=[{'Name':'instance-state-name','Values': ['running']}]) all_list = [] cnt = 0 for reservation in responce['Reservations']: for instance in reservation['Instances']: ec2_tags = dict([(tag['Key'], tag['Value']) for tag in instance['Tags']]) if ec2_tags!='': if cnt == 0: all_list.append(" <b>**** EC2 **** </b>") all_list.append(ec2_tags['Name']) cnt+=1 ############ RDS ############## rds = boto3.client('rds') responserds = rds.describe_db_instances() cnt = 0 for i in responserds['DBInstances']: if i['DBInstanceStatus'] == 'available': db_instance_name = i['DBInstanceIdentifier'] if cnt == 0: all_list.append("") all_list.append(" <b>**** RDS **** </b>") all_list.append(db_instance_name) cnt+=1 ############ Teams に通知 ############## if all_list == []: # 起動中のものがない場合は通知しない pass else: request_data = { 'title': "以下のリソースが起動中です。<br>", 'text': ' <br>'.join(all_list) } request = Request( teams, data=json.dumps(request_data).encode('utf-8'), method="POST" ) try: with urlopen(request) as response: response_body = response.read().decode('utf-8') logger.info(response_body) except HTTPError as err: logger.error(err.code) except URLError as err: logger.error(err.reason)
そんなに特別なことをしているわけではありませんが、少しだけ部分的にみていきます。
http = urllib3.PoolManager()
teams="https://xxxxxx/xxxx/xxxx/"
urllib3はHTTP通信用のモジュールのようで、今回は Teams にHTTPで連携するので定義してます。
下段で定義しているURL は Teams のチャネルでWebhook を構成したときに取得できるURLです。後ほど取得方法も説明します。
client = boto3.client('ec2') responce = client.describe_instances(Filters=[{'Name':'instance-state-name','Values': ['running']}])
boto3 というAWS SDK を使用して EC2 や RDS のリソース情報を取得し、 describe_instances で起動中のインスタンスで絞ってインスタンス情報を取得しています。
describe_instances の詳しい内容は下記リファレンスを参照してください。
for reservation in responce['Reservations']: for instance in reservation['Instances']: ec2_tags = dict([(tag['Key'], tag['Value']) for tag in instance['Tags']]) if ec2_tags!='': if cnt == 0: all_list.append(" <b>**** EC2 **** </b>") all_list.append(ec2_tags['Name']) cnt+=1
'Reservations'、'Instances' はお決まりっぽい。レスポンスがこうなので。
最終的にはインスタンス名だけを取得して、配列に追加していきます。
RDS も EC2 とほぼほぼ同じ構成です。なので説明は割愛します。
request_data = { 'title': "以下のリソースが起動中です。<br>", 'text': ' <br>'.join(all_list) } request = Request( teams, data=json.dumps(request_data).encode('utf-8'), method="POST" )
上段は Teams に投げるタイトルとメッセージ内容です。 インスタンス名が配列にセットされているので、それぞれ改行しながら抜き出している形です。
下段は、Teams に実際に投げるリクエストの定義です。中身のデータはjson.dumps でJSON に変換してエンコードします。
try: with urlopen(request) as response: response_body = response.read().decode('utf-8') logger.info(response_body) except HTTPError as err: logger.error(err.code) except URLError as err: logger.error(err.reason)
最後は Teams にリクエストを投げるところです。
これで、リソースの状況をチェックして Teams に連携するところまでができました。これでLambda でテストを実行すれば、アドホックですがTeams に通知を出すことができます。
EventBrigde で定期実行の設定を行う
Lambda の画面からトリガーを追加をクリックします。
※設定後なので既にEventBridgeが表示されてます
EventBridge を選択します。
あとは新規ルール作成でスケジュールを選択し、実行したいタイミングをCron式で設定します。なお時間はGMT なので、ローカルタイムゾーンでちゃんと時間が合ってるか確認した方が良いですね。
これで全ての設定が完了しました!
通知イメージ
あとは時間まで待つと、こんな感じで Teams に通知がきます!
※色々インスタンス名に個人名など入ってたのでぼかしてます
Docker ボリュームマウントしたときのバックアップ方法
前回はボリュームマウントを試してみたので、今回はそのマウントしたボリュームをバックアップする方法についてやってみます。
ボリュームのバックアップ方法
バインドマウントと違ってボリュームの場合は、対象のパスをそのまま tar などで固めてもバックアップファイルとしては有効ではないようです。
というのも、Docker Engine からバックアップを実行しているので、Docker Engineとしてのシステム領域をバックアップするということになり、対象のコンテナにリストアできないかもしれないのです。
バックアップ方法ですが、適当なLinux系のコンテナを起動して、そこに対して対象コンテナをマウントしてバックアップする形となります。
まずは対象コンテナが停止していることを確認します。
[ec2-user@~]$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a6a1c6b8eb7a mysql:5.7 "docker-entrypoint.s…" 7 minutes ago Exited (0) 20 seconds ago mysqldb [ec2-user@~]$
Linux のコンテナをここで起動します。バックアップとしてしか使用しないので、 busybox という軽量 Linux を使用します。
[ec2-user@~]$ docker run --rm -v samplevol:/src -v "$PWD":/dest busybox tar czvf /dest/backup.tar.gz -C /src . ~ [ec2-user@~]$ ll total 7036 -rw-r--r-- 1 root root 7202585 Jan 13 21:46 backup.tar.gz drwxr-xr-x 2 root root 24 Jan 9 21:49 bindData [ec2-user@~]$
backup.tar.gz がカレントディレクトリに作成されているのが確認できました。
ちなみにそれぞれのオプションの意味ですが、
- rm :コマンド実行後、コンテナを削除します
- samplevol:/src :samplevol ボリュームを /src にマウントする
- "$PWD":/dest busybox : Docker ホストのカレントディレクトリを /dest にバインドマウントする
- tar czvf /dest/backup.tar.gz -C /src . : /src 以下を /dest/backup.tar.gz にバックアップする(/dest はカレントディレクトリにマウントしてる)
リストアしてみる
まずはボリュームを使用しているコンテナとボリュームを削除してみます。
[ec2-user@~]$ docker rm mysqldb mysqldb [ec2-user@~]$ docker volume rm samplevol samplevol [ec2-user@~]$
次にボリュームを作成します。
[ec2-user@~]$ docker volume create samplevol samplevol
ここで、先ほどバックアップしたファイルを使ってリストアしてみます。
[ec2-user@~]$ docker run --rm -v samplevol:/dest -v "$PWD":/src busybox tar xzf /src/backup.tar.gz -C /dest [ec2-user@~]$
/dest にボリュームマウント、/src にバインドマウントとし、カレントディレクトリにある backup.tar.gz が /src/backup.tar.gz として扱うことができます。
/dest はリストア先のボリュームにマウントしているので、/dest に展開されていきます。
これでボリュームをリストアしたので、MySQLコンテナを起動してみます。
[ec2-user@~]$ docker run --name mysqldb -dit -p 3306:3306 -v samplevol:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=pwsswd mysql:5.7 9aa13f0f109e531714cd22c241dbffbdcf2d23e349b20d316df3a152aa4069c6 [ec2-user@~]$
MySQL WorkBench で接続してみると、前に作成したテーブルが新しいコンテナでも見れるようになっていることを確認できました。
Docker ボリュームマウントしてDBデータを永続化してみる
今回は MySQL コンテナでボリュームマウントについて試してみます。ちなみに、前回はバインドマウントをやってみました。
ボリュームマウントとは
Docker ホストで確保した領域を利用することで、前回試したバインドマウントはDocker ホストとは関係なしにホスト上のディレクトリをマウントする方法でした。
ボリュームマウントを使うメリットは、Docker で管理するので特にパスなどを考える必要がないということです。
自分のようにすぐにパスを忘れてしまうようなら、ボリュームマウントの方が必須かもですね。というより、どちらも利用ケースはあるにせよ、全体的にこっちの方が使いやすいのかな。
けど、Docker ホストのある場所でファイルやデータを格納してやりとりしたいときは、バインドマウントの方がわかりやすいかも。
なので使い分けは、
- 単純にコンテナのデータを保存 → ボリュームマウント
- データのやり取り、保存データをホストで編集 → バインドマウント
ですかね。
ボリューム関連のコマンド
コマンド | 内容 | サンプル |
---|---|---|
cretae | ボリュームを作成 | docker volume create samplevol |
inspect | ボリューム情報を確認 | docker volume inspect samplevol |
ls | ボリュームリスト表示 | docker volume ls |
prune | マウントされてないボリュームを削除 | docker volume prune |
rm | ボリューム削除 | docker volume create samplevol |
ボリュームマウントでコンテナを起動
まずはボリュームを作成します。
[ec2-user@~]$ docker volume create samplevol samplevol [ec2-user@~]$ docker volume ls DRIVER VOLUME NAME local samplevol [ec2-user@~]$
今回は MySQL コンテナを使ってデータ登録などを行い、その後コンテナを入れ替えてもデータが永続化されてるか確認します。
ではボリュームマウントで MySQL コンテナを起動します。
[ec2-user@~]$ docker run --name mysqldb -dit -p 3306:3306 -v samplevol:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=pwsswd mysql:5.7 d90f630bca70b3a53834584434ffba3a4033c90bac4be37cb5beb906bd4998d1 [ec2-user@~]$
MySQL コンテナに入ってデータを登録
コンテナ起動時に -p で3306 でパブリッシュしてるので MySQL WorkBench からアクセスすることができます。※EC2 のセキュリティインバウンドルールにポート3306 は追加しておく必要はあります。
適当にスキーマとテーブルを作成
もし、teraterm で直で MySQL コンテナに入る場合は docker exec コマンドを使います。
[ec2-user@~]$ docker exec -it mysqldb /bin/bash root@d90f630bca70:/# root@d90f630bca70:/# root@d90f630bca70:/# mysql -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 5 Server version: 5.7.32 MySQL Community Server (GPL) Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
コンテナ再作成
ではコンテナを削除→新規作成しても、先ほど作成したデータが表示されるか確認します。
まずはコンテナを削除
[ec2-user@~]$ docker stop mysqldb mysqldb [ec2-user@~]$ docker rm mysqldb mysqldb [ec2-user@~]$ [ec2-user@~]$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d256c64c0371 httpd:2.4 "httpd-foreground" 24 hours ago Exited (0) 24 hours ago webapp2 [ec2-user@~]$
さっき作成したボリュームをマウントして mysqldb2 という名前で MySQL コンテナを起動してみます。
[ec2-user@~]$ docker run --name mysqldb2 -dit -p 3306:3306 -v samplevol:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=pwsswd mysql:5.7 3e442b1084df59a740b96c11218fc2a773133ad587ce90a62b68169b1f1cc6cc [ec2-user@~]$
MySQL Workbench でアクセスしてみると、先ほど作成したテーブルやレコードが表示されました!
Docker バインドマウントしてデータを永続化してみる
前回はApache コンテナを起動してみました。
このままだと一度コンテナを削除するとファイルや設定情報などは消えてしまいますので、バインドマウントをしてデータ保存先を Docker から分離してみたいと思います。
バインドマウントしてみる
最初に対象のディレクトリを作成してみます。
[root@ec2-user]# mkdir bindData [root@ec2-user]# ll total 0 drwxr-xr-x 2 root root 6 Jan 9 21:01 bindData [root@bindData]# pwd /home/ec2-user/bindData
作成した以下ディレクトリをバインド先にします。
/home/ec2-user/bindData
ではコンテナを起動してみようと思いますが、その前に前回作成したコンテナが残ってたので削除しちゃいます。
[root@bindData]# docker stop webapp1 webapp1 [root@bindData]# docker rm webapp1 webapp1 [root@bindData]#
これできれいになったのでコンテナを起動してみますが、その際に赤字部分にマウントするパスをセットしてあげます。
docker run -dit --name webapp1 -p 8080:80 -v /home/ec2-user/bindData:/usr/local/apache2/htdocs/
httpd:2.4
[root@bindData]# docker run -dit --name webapp1 -p 8080:80 -v /home/ec2-user/bindData:/usr/local/apache2/htdocs/ httpd:2.4 1e6796d3fd27a9d0a630d7c6809d53aa86eb723345e7da93300363445f295bdd [root@bindData]# [root@bindData]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1e6796d3fd27 httpd:2.4 "httpd-foreground" 5 seconds ago Up 4 seconds 0.0.0.0:8080->80/tcp webapp1 [root@bindData]#
コンテナは起動されましたので、URL:ポートでアクセスしてみます。
何もないのでこうなりますよね。ではindex.html を置いてみます。
[root@bindData]# ll total 4 -rw-r--r-- 1 root root 154 Jan 9 21:49 index.html [root@bindData]# cat index.html <html> <head> <meta http-equiv="content-type" charset="utf-8"> </head> <body> <div>Docker バインドマウント</div> </body> </html> [root@bindData]#
再度ブラウザをリロードしてみると表示されました。
コンテナを新たに作成
今起動しているコンテナを停止、削除後、再度Apacheコンテナを起動します。(今回はwebapp2という名前にしてみました)
[root@bindData]# docker stop webapp1 webapp1 [root@bindData]# docker rm webapp1 webapp1 [root@bindData]# [root@bindData]# docker run -dit --name webapp2 -p 8080:80 -v /home/ec2-user/bindData:/usr/local/apache2/htdocs/ httpd:2.4 d256c64c0371a6e961ee66b6fc1e84f85fa9810ba165e23df91e3f0e739fbf41 [root@bindData]#
新たに作成したコンテナでも index.html ちゃんと表示されました。
おわり。
Amazon Linux にDocker を入れてApache コンテナを起動してみた
EC2でDocker を使ってWebサーバを作ってみたのでメモ。
EC2 準備
まずは EC2 で VM を用意します。今回は Amazon Linux を利用。
ポートはとりあえずデフォルトの22 に加えて、8080 も追加。
キーは以前作成してたのでそれを流用しましたが、なければ新規作成する。
これで VM の用意が完了です。
Docker Engine のインストール
まずは Teraterm で作成した VM にアクセスします。
Amazon Linux の場合、ユーザー名は ec2-user になります。あとは VM 作成時に指定した秘密鍵を設定。
以下コマンドで Docker Engine をインストールします。
$ sudo yum update -y
$ sudo amazon-linux-extras install docker
$ sudo yum install docker
インストール後、バージョンを確認する場合
# docker --version Docker version 19.03.13-ce, build 4484c46
今回は Amazon Linux へのインストールだったので、手順はこちらを参考にしました。
docs.aws.amazon.com
もし、一般ユーザーでdocker コマンドが実行できない場合は、docker グループに入れてあげることで実行できるようになります。
$ sudo usermod -a -G docker ec2-user
Webサーバ起動
いまは VM で Docker を動かすための Docker Engine をインストールまでしたところなので、以下のコマンドで Docker Hub から Apache の Docker Image をダウンロードして実行します。
docker run -dit --name webapp1 -p 8080:80 -v "$PWD":/usr/local/apache2/htdocs/ httpd:2.4
- -dit : コンテナをバックグラウンドで実行させる
- --name : コンテナ名
- -p : Docker ホストとコンテナのポート番号。外部からは8080 でアクセスし、ホストからは 8080 と紐づいたコンテナのポート80 にアクセス
- -v : ホストのディレクトリ、コンテナのディレクトリ
それでは実行してみます。
[ec2-user@ip-172-31-42-9 ~]$ docker run -dit --name webapp1 -p 8080:80 -v "$PWD":/usr/local/apache2/htdocs/ httpd:2.4 docker: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?. See 'docker run --help'.
もし上記のようなエラーが発生した場合、Docker 自体が起動していないという意味ですので、起動してあげます。
[ec2-user@ip-172-31-42-9 ~]$ sudo service docker start Redirecting to /bin/systemctl start docker.service [ec2-user@ip-172-31-42-9 ~]$
実行して ID が返ってきたら起動できたということですので、 ステータスをみてみます。
[ec2-user@ip-172-31-42-9 ~]$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a374217edee6 httpd:2.4 "httpd-foreground" About a minute ago Up About a minute 0.0.0.0:8080->80/tcp webapp1
Status がUp になってるので起動しているのが確認できます。
ちなみに、-a を最後に付与すると停止中のコンテナも表示されます。
この状態で EC2 のIPアドレスと8080でブラウザからアクセスするとWeb ページが表示されるようになります。(まだ何もしてないのでカレントディレクトリの中身が表示されてしまいますが・・)
もし以下のように 403 エラーが出たら、対象ディレクトリのパーミッションを変更してあげます。
[ec2-user@ip-172-31-33-236 home]$ chmod 755 ec2-user/ [ec2-user@ip-172-31-33-236 home]$ ll total 0 drwxr-xr-x 2 root root 6 Jan 9 04:33 centos drwxr-xr-x 4 ec2-user ec2-user 109 Jan 9 04:24 ec2-user [ec2-user@ip-172-31-33-236 home]$
あとは、ホスト側の対象ディレクトリ内にindex.html 置くなどして利用できます。
OnsenUIを触ってみたのでタグとかのメモ
結構前に初めて monaca で Onsen UI を触ってみましたので、ほんの少しだけタグ中心のメモです。
Onsen UI とは
各種タグや処理の説明
<ons-navigator>
複数のHTML ファイルを操作する役割を持つ。画面上には表示されない。また、次ページ前ページへのページ遷移機能も持っている。
<ons-navigator page="初期表示ページ"></ons-navigator>
<ons-page>
Onsen UI でページを定義するために必要なタグとなる。
<ons-page> ・ ・ ・ ・ </ons-page>
<ons-toolbar>
画面上部に表示されるツールバー。
<ons-toolbar> <div class="center">画面1</div> </ons-toolbar>
<ons-button>
画面に表示されるボタン。
<ons-button> OKボタン</ons-button>
OKボタン押下でページ遷移する場合
<ons-button onclick="document.getElementById('id').pushPage('B.html')"> OKボタン </ons-button>
TOP画面に戻る。内部で保持していたページの履歴もリセットされる。
<ons-button onclick="document.getElementById('id').resetToPage('TOP.html')"> TOPボタン </ons-button>
<ons-back-button>
前の画面に戻るボタン。
<ons-back-button>Back</ons-back-button>
<ons-list>と<ons-listi-tem>
リスト形式でデータを一覧表示するためのタグ。リスト内のデータを1件ずつ扱う場合は、<ons-listitem>。
<ons-list> <ons-list-item>項目1</ons-list-item> <ons-list-item>項目2</ons-list-item> <ons-list-item>項目3</ons-list-item> </ons-list>
初期処理
Onsen UI でのInit処理。
document.addEventListener("init", function(event) { if (event.target.id == "ページの ID") { //ページの初期処理 } });