XPath と Lens で XML を扱う
はじめに
OpenGL の仕様が XML で書かれていてこれについて調べたりしてました。 XML 扱う機会があまりなくてほとんど使ったことがなかったんですが調べてみると色々と XML 使われているところはあるのだなと感じました。 XML から検索したりコード生成したりしたくて調べたことをまとめておきます。
XPath
XPath は XML Path Language の略です。 XPath を使って XML から必要な情報を検索したりできるようです。
JSON でいうところの JMESPath や jq などに近いものだと思います。
XPath は HTML にも使えるようで JavaScript やクローラでも使われたりする例を見かけました。 XPath 3.1 までバージョンが出てるらしいんですが、新しいものは対応しているツールがあまり無いようでした。
CLI から XPath を扱うことができるツールがあって XMLStarlet や xmllint が使えるようです。
xmlstarlet では XML の更新も行える機能があるようなのでこちらを使ってみてます。
問い合わせの場合は sel のサブコマンドを使います。 ドキュメントはこちらです。
Lens
XPath を使って CLI で検索したりはできたんですが Haskell などから扱う場合にどうするのかなと調べていたところ Lens を使うと似たようなことができそうでした。
Lens についてはあまりよくわかっていないので詳しくは他の情報を参照してください。 とりあえず使うだけであれば Getter や Setter として使えるというぐらいの理解でもいいような気がします。
XML を扱う Haskell のライブラリはいくつかあるんですがあまり更新されてないものもあってどれを使うか迷いました。 XML 自体の仕様が変わっているわけでは無いので最近更新されていなくてもそんなに問題なく使えるような気もします。
xml-conduit は比較的更新されていそうだったためこれを使ってみています。
xml-conduit の Lens ライブラリとして xml-lens がありました。
Lens 自体のライブラリはこれです。
なお JSON にも同じように aeson というパーサーのライブラリと lens-aeson という Lens のライブラリがあります。
XPath と Lens の対応
<?xml version="1.0" ?> <animals> <animal id="1"> <name>rat</name> </animal> <animal id="2"> <name>cow</name> </animal> <animal id="3"> <name>tiger</name> </animal> <animal id="4"> <name>rabbit</name> </animal> </animals>
上のような XML をサンプルとして扱う場合の一部の例です。
XPath | Lens | |
---|---|---|
ルートの要素 | / |
doc ^.? root |
要素の階層を指定して取得 | /animals/animal/name |
doc ^.. root . el "animals" ./ el "animal" ./ el "name" |
属性に条件を付けて取得 | /animals/animal[@id = "2"]/name |
doc ^.. root . el "animals" ./ el "animal" . attributeIs "id" "2" ./ el "name" |
doc ^.. root . el "animals" ./ el "animal" . filtered ((Just "2" ==) . (^? attr "id")) ./ el "name" |
||
子の要素に条件を付けて属性を取得 | /animals/animal[name = "cow"]/@id |
doc ^.. root . el "animals" ./ el "animal" . filtered ((== Just "cow") . (^? plate . el "name" . text)) . attr "id" |
名前が一致する要素を取得 | //animal |
doc ^.. root . entire . named "animal" |
データ型に変換する
ほとんどの場合は xml-conduit の結果の型そのままだと使いにくいと思います。 ここから自分で使いたいデータ型に変換して使うことになると思うのでデータ型に変換するあたりのコード例を書いておきます。 Maybe モナドや Either モナドでパーサーと似たような処理を書いて変換できました。
import Data.Text import qualified Text.XML as XML import Text.XML.Lens data Animal = Animal { animalId :: !Int , animalName :: !Text } deriving (Show) parseAnimal :: XML.Element -> Maybe Animal parseAnimal a = do aid <- a ^? attr "id" aname <- a ^? el "name" . text return (Animal aid aname) parseAnimals :: XML.Document -> Maybe [Animal] parseAnimals doc = mapM parseAnimal elems where elems = doc ^.. root . el "animals" ./ el "animal"
ECS で JMeter を動かしてみる
ECS を使った負荷テストについてです。 負荷テストでは十分な負荷を発生させることができれば 実行環境はある程度自由に選択できるので Docker で色々なツールを実行する というのも選択肢の一つになると思います。
Locust など新しめのツールを ECS で使うという記事は他にも書かれているのを見かけたのですが JMeter はあまり見かけないので書いてみます。 JMeter をそんなに使いたいというわけではないのですが、すでに作ったシナリオを利用したいなど使うことはあるかなと思います。
ECS は去年少し触ってみてからあまり使う機会がなくてすでに EKS も使えるようになっていました。
Kubernetes を負荷テストに使うという記事も見かけたことがあるので、Kubernetes 使う方が GCP などでも活用しやすいかもしれません。
今回は ECS まだあまりつかったことがなくてもう少し使い方を覚えたいということで ECS でやってみてます。 それから JMeter 以外のツールもこれから使うと思うのでいくらか触れています。
今回使ったテンプレートや Dockerfile などは下のリポジトリにおいています。
分散負荷テストのための構成
負荷テストツールでは大きな負荷をかけられるようにするため多くは分散テスト (複数のサーバーから負荷をかける) ができるようになっています。 ツールによってこの仕組みが色々違っているので使うものに合わせて実行環境を用意する必要があります。
JMeter
JMeter のウェブページはここです。
JMeter を動かすときの構成を図にしてみました。
JMeter に外からアクセスする必要はないためプライベートサブネット内にクラスターをおいています。 図に書いてないですが ECR からのイメージダウンロードや負荷テスト時にはインターネットアクセスは必要なので NAT を使っています。 図ではマスターも同じ ECS クラスターで実行していますが、 マスターは手動でコマンドを色々変えて実行したいということもあると思います。 マスターだけパブリックサブネットに踏み台のような EC2 サーバーを用意してそこから実行するというやり方もあると思います。
JMeter ではまずスレーブをサーバーモードで立ち上げて マスターを実行するときにスレーブのIPアドレスとポートの一覧を指定して接続できるようにするという流れになります。 また JMeter の場合はマスターのプロセスを実行したときに負荷テストもスタートして計測結果がマスターに集められログのファイルに保存されます。
JMeter の場合は
- スレーブの IP とポートの一覧を取得できるようにする
- マスター実行時に結果をログに出力するか S3 などにアップロードする
- マスター・スレーブ間で通信できるようにポートなど設定する
ということが必要そうです。
ECS上のタスクのIPとポートは ecs-cli から取得することができるようです。 色々取得方法があると思いますがサービスディスカバリに登録するようにして aws-cli から取得するということもできました。
上のリポジトリで VPC、サブネット、NAT、ECS クラスター、オートスケーリンググループなどの CFn テンプレートの例が公開されていました。 これを真似して一通り構築することができました。
Locust
Locust のページはこちらです。
Locust ではマスターを先に立ち上げ、スレーブの起動時にマスターのIPとポートを指定して接続することになります。 Locust のマスターはウェブサーバーとしても機能してウェブページから負荷テストをスタートさせられます。 負荷テスト結果もウェブページから見ることができます。
ECS を使った例を色々と探していて Locust の記事がいくつか見つかったため参考にさせてもらいました。
Locust と ECS を使った負荷テストについてこちらの記事で詳しく解説されていて参考になりました。CFn テンプレートも記載されています。
マスターへのインターネットからアクセスが可能なようにマスターのみ Fargate モードでパブリック IP を付けて動作させているようです。
マスターとスレーブはそれぞれタスク定義を分けて別のサービスにしているようです。
現在ではサービスディスカバリも利用できるようになっているのでスレーブからマスターへのアクセスは サービスディスカバリで行うこともできると思います。
こちらの記事も同じく ECS と Locust を使った例です。
こちらの記事では docker-compose.yml でマスター・スレーブ構成を定義して使うようにしていました。
ecs-cli では docker-compose.yml の定義をタスクとして実行するという機能があるようです。
docker-compose を使うことができるとローカルでも同じ構成で確認できて便利だと思います。
ただし一つのタスク定義内に入れられるコンテナ数は10以下に制限されるという点には注意する必要があると思います。
大きな負荷が必要な場合にはタスク定義を分けておいたほうがいいと思います。
Fargate と EC2 どちらを使うか
構成は Fargate の方が簡単になるとは思います。 アカウント毎の Fargate タスク数の制限が前はデフォルト20まででしたが今は50になっていて少し使いやすくなっています。 料金も値下げされたようです。
ただ EC2 で動かす場合もクラスター構築の CFn テンプレートなどを用意しておけばある程度簡単にセットアップできると思います。 また EC2 の場合はスポットインスタンスを使って安く使うことができる場合があります。 状況によってどちらがいいかというのは変わりそうなので EC2 での手順も用意しておいて Fargate で簡単に対応できる要件であれば Fargate でやるなど選択することになるかなと思います。
今回は手順を色々覚えようということで EC2 で試しました。
Dockerfile
JMeter を動かすため Docker イメージには JRE が必要になります。 OpenJDK のイメージが公開されていたのでこれを元にその他必要なものを入れました。 Java のイメージの作り方に詳しくないのでもっとイメージサイズを小さくする方法があるのかもしれません。
FROM openjdk:11-jre-slim ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && apt-get install -y --no-install-recommends \ awscli curl groff-base jq less unzip wget zip \ && rm -rf /var/lib/apt/lists/* RUN mkdir /jmeter \ && cd /jmeter \ && wget https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-5.0.tgz \ && tar --strip-components=1 -xvf apache-jmeter-5.0.tgz \ && rm apache-jmeter-5.0.tgz ENV JMETER_HOME /jmeter ENV PATH $JMETER_HOME/bin:$PATH WORKDIR /work ADD scenarios /work/scenarios ADD start-slave.sh /usr/local/bin ADD run-master.sh /usr/local/bin ENTRYPOINT [] CMD ["start-slave.sh"]
スレーブ起動用のスクリプトは以下のようになっています。
java.rmi.server.hostname
にプライベート IP を指定するため
EC2 メタデータ API で取得しています。
この方法は Fargate の場合は使えないので Fargate で使う場合修正必要だと思います。
#!/bin/bash set -x SERVER_IP=$(curl -q http://169.254.169.254/latest/meta-data/local-ipv4) SERVER_PORT=1099 jmeter -Dserver_port=$SERVER_PORT -Jserver.rmi.ssl.disable=true \ -D"java.rmi.server.hostname=$SERVER_IP" \ -j /dev/stdout -s "$@"
タスク定義
パラメーターなど省略しますがタスク定義の CFn テンプレートは下のような感じにしました。 ネットワークモードは最初デフォルトのブリッジで試していたのですがマスターとスレーブ間の通信が上手くいかずホストモードを使うようにしました。 1099のポートを固定で使用しているため、タスクの配置が制限されてしまうのでできれば動的ポートマッピングにしたかったんですが RMI 関連の知識がないため上手くやる方法を見つけられませんでした。
マスターのタスク起動時にスレーブのIP一覧や負荷をかけるホストのIPをオーバーライドのパラメーターで渡します。 下のような感じで cli から実行しました。
aws ecs run-task --cluster {クラスタ名} \ --task-definition jmeter-master \ --overrides '{"containerOverrides":[{"name":"jmeter-master","command":["run-master.sh","-GTARGET_HOST={ターゲットIP}","-GTARGET_PATH={ターゲットパス}","-R{スレーブIP一覧}"]}]}'
スレーブのタスクはサービスにして動かしたのですが スレーブごとにパラメーターを変えて動かしたい場合などもあるかもしれないので run-task で一つづつ起動するやり方でもいいと思います。
MasterTaskDef: Type: AWS::ECS::TaskDefinition Properties: Family: 'jmeter' Cpu: !Ref 'ContainerCpu' Memory: !Ref 'ContainerMemory' NetworkMode: 'host' ContainerDefinitions: - Name: 'jmeter-master' Cpu: !Ref 'ContainerCpu' Memory: !Ref 'ContainerMemory' Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${RepositoryName}' LogConfiguration: LogDriver: awslogs Options: awslogs-region: !Ref 'AWS::Region' awslogs-group: !Ref 'LogGroup' awslogs-stream-prefix: !Ref 'AWS::StackName' EntryPoint: - run-master.sh SlaveTaskDef: Type: AWS::ECS::TaskDefinition Properties: Family: 'jmeter' Cpu: !Ref 'ContainerCpu' Memory: !Ref 'ContainerMemory' NetworkMode: 'host' ContainerDefinitions: - Name: 'jmeter-slave' Cpu: !Ref 'ContainerCpu' Memory: !Ref 'ContainerMemory' Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${RepositoryName}' PortMappings: - ContainerPort: 1099 HostPort: 1099 LogConfiguration: LogDriver: awslogs Options: awslogs-region: !Ref 'AWS::Region' awslogs-group: !Ref 'LogGroup' awslogs-stream-prefix: !Ref 'AWS::StackName' EntryPoint: - run-slave.sh
まとめ
色々と時間がかかってしまいましたが ECS で JMeter を動かすことができました。 CFn テンプレートなどで手順をまとめておくと色々な要件で使いまわすことができて気軽に試すことができると思います。 JMeter 以外のツールを使う場合も Docker イメージとタスク定義あたりを変えることで活用できると思います。
その他に負荷テストを提供しているサービスも色々あるようです。 CodePipeline とも連携している BlazeMeter というサービスでも JMeter などが使えるようです。 おそらくこうしたサービスだと環境の構築は自分でやらなくていいようになっているのだと思います。
AWS S3 への匿名での Put
概要
AWS S3 に匿名で Put すると危ないという話についてです。
私もこれを間違ってやってしまってました。
AWS のドキュメントにも少し書かれていましたが間違って使ってしまっている人もいるんじゃないかと思います。
注記
バケットをパブリックにした場合 (非推奨)、認証されていないどのユーザーもバケットにオブジェクトをアップロードできます。これらの匿名ユーザーは AWS アカウントを持っていません。匿名ユーザーがバケットにオブジェクトをアップロードすると、Amazon S3 によって特殊な正規ユーザー ID (65a011a29cdf8ec533ec3d1ccaae921c) がそのオブジェクトの所有者として ACL で追加されます。
どのように危険か
匿名ユーザーで Put したオブジェクトの ACL 情報を取得してみると以下のようになっています。
{ "Owner": { "ID": "65a011a29cdf8ec533ec3d1ccaae921c" }, "Grants": [ { "Grantee": { "ID": "65a011a29cdf8ec533ec3d1ccaae921c", "Type": "CanonicalUser" }, "Permission": "FULL_CONTROL" } ] }
ドキュメントに書かれているように匿名ユーザーがオーナーになっていて、このオーナーの匿名ユーザーに FULL_CONTROL
の権限がついています。
つまり匿名ユーザーが読み込みも書き込みもできてしまう状態です。
この匿名ユーザーは実は特定のユーザーになっているので オブジェクトのリージョンとバケット、オブジェクトキーの情報が知られてしまうと誰でもアクセスできてしまいます。
バケットポリシーで特定の IP 以外は拒否などの設定が入っていれば制限できると思いますが バケットポリシーで IP 許可だけの設定だと、オブジェクトの権限だけで通ってしまうようです。
結構間違って使われているのでは...
匿名ユーザーの ID で検索すると list-objects の結果と思われるような情報が色々でてきます。 list-objects の結果情報にはオーナーとオブジェクトキーも含まれているので匿名ユーザーでこれを書き換えたりできてしまうかもしれません。
間違った場合
匿名で Put できないようにバケットポリシーで制限します。 それからすでに匿名ユーザーで作られてしまったオブジェクトのオーナーは更新できないようなので 一度ダウンロードして、別のユーザーでアップロードし直しオーナー変更します。 list-objects の結果にオーナーIDも含まれているので問題のあるオブジェクトを探すのに使えます。
S3のアクセスコントロールについて参考
Android で朝起きるときに音楽を流したい
概要
スマート家電などを活用している人の話に影響されて 朝起きる時に音楽を流したり照明を自動で点けたりしたいと思い色々試してました。
スマートスピーカーがあるとアラームとして音楽を流すなど簡単にできそうですが スマートスピーカーは持ってないため手持ちの Android タブレットと Bluetooth スピーカーでやる方法を試しました。 Automate でアラームをトリガーにして音楽を流すというやり方で目的のことができました。
アラームアプリ
最初から入っていたアラームのアプリや Google が出している時計のアプリもあってこれで十分の場合もあると思います。
Spotify のアプリと連携してプレイリストを再生するなどもできるようです。
ただし最初から入っていたアラームアプリだと外部スピーカーに音声出力するという設定ができませんでした。 またできれば Amazon Music も使えるようにしたいですが今のところはできないと思います。
IFTTT で Android Device 連携 (上手くいかなかった)
IFTTT (If this, then that) というサービスがあってスマート家電などと一緒によく使われているようです。 トリガーとなるサービスと、そのときに実行するアクションを組み合わせて色々なことができるようになっています。
Android 向けのアクションとして Play music もありました。 時間をトリガーにしてこのアクションを使えば目的のことができそうだったんですが 機種の問題なのかわかりませんが上手く動きませんでした。
上手くいかなかったのは Android を長時間スリープ状態にしておくと トリガーを設定した時間になってもスリープ解除されず音楽も再生されないというところです。 トリガー時間をすぎて手動で電源を入れるとその時点から音楽が流れますが これでは目覚ましになりません。
それからどの音楽アプリを使うかというのが設定しにくくなっています。 最初にトリガーされたときにどの音楽アプリを使うか選択する画面がでてきますが これを毎回選択するようにすると目覚ましにはできませんし、固定にするとあとで変えたりできなくなります。 音楽アプリを新しく追加すると選択画面がまた出るようになりますが、 このせいで選択画面で止まってしまい次の朝に目覚ましとして動かないという感じでやはり使いづらいです。
Automate
Automate ⋅ everyday automation for Android ⋅ LlamaLab
Automate は Android 上での色々なタスクの自動化などに使えるアプリです。 IFTTT より複雑で一から使い方を覚えるのは大変そうですが 他のユーザーが作成したフローを真似して作れば一応は使えそうです。
Automate にはアラームをトリガーにするブロックがありました。 標準のアラームではスリープから復帰できていたので、アラームと同時に起動するようにすれば IFTTT のときのようにスリープ抜けないという問題は回避できるのではないかと思いました。
Amazon Music で検索するといくつかフローがあったため音楽を流すところを参考にして トリガー部分をアラームにしてフロー作成しました。フローは下のようになりました。
アラーム側は無音でバイブレーションもなしで画面だけ明るくなるという設定にして動作させています。
まだ使って一週間ほどですが大体問題なく動いています。 (スリープのせいか起動が少し遅れたりはしますが)
CloudFormation のリソース仕様からコード生成してみる
はじめに
この間 AWS-CDK というプロジェクトが公開されていました。
aws.amazon.com github.com https://awslabs.github.io/aws-cdk/awslabs.github.io
まだ開発者プレビューという段階で正式な公開ではないようです。 中身はあまり詳しく見ていませんが、かなり便利に使えそうな印象がありました。 TypeScript, JavaScript, Java, .NET, Python などの言語で CloudFormation のリソースを記述して、これを元に色々なリソースを AWS 上に構築するのに使えるようです。
CloudFormation のテンプレートは YAML や JSON で書くことができますがなかなか大変です。 少し複雑なものを作ろうとするとリファレンスを参照しながら、トライアルアンドエラーでスタックを何度も立てたりして書いていた記憶があります。 色々な言語で IDE の補間やシンタックスチェックなどが効いた状態で書ければ便利になりそうです。 またプログラミング言語で書けるので、他のリソース情報を参照したりということもやりやすくなるのではないかと思います。
すこし調べていたところ CloudFormation のリソース仕様が公開されていると知りました。
cfn-lint のツールなどでもこの仕様が利用されているようです。 AWS-CDK の内部でもおそらくこの仕様からコード生成したりして利用されているのではないかと思います。
実際に使う場合には CDK などの公式のツールを使うのがいいと思いますが この仕様からコード生成するというのはそんなに難しくなくできそうなのと、ちょっと面白そうという気がしてコード生成するのを試してみました。
リソース仕様の構造
リソース仕様の構造についての詳しい説明はどこにあるかわかりませんでしたが、中身を見てみると意味はなんとなく理解できました。
一番上の階層に PropertyTypes
, ResourceTypes
, ResourceSpecificationVersion
があります。
{ "PropertyTypes": {...}, "ResourceTypes": {...}, "ResourceSpecificationVersion": "2.8.0" }
ResourceTypes
は色々なリソースの定義です。 PropertyTypes
はリソースの中で使われているプロパティの定義のようです。
ResourceSpecificationVersion
はこの仕様のバージョンだと思います。
基本の型など
基本の型 (PrimitiveType
) として Boolean
, Double
, Integer
, Long
, String
, Timestamp
, Json
があるようです。
このほかに Map
と Tag
は PrimitiveType
ではないですが定義なしで使われていて少し特殊な扱いのようでした。
Amazon API Gateway Deployment DeploymentCanarySettings - AWS CloudFormation
Map
は例えば上のような部分で使われていました。キーと値が両方文字列の連想配列のようです。
AWS CloudFormation Resource Tags タイプ - AWS CloudFormation
Tag
はキーと値の文字列のペアです。
それから List
タイプがあります。List
は配列です。List
の場合は ItemType
または PrimitiveItemType
で要素の型が書かれていました。
プロパティタイプ定義
プロパティタイプ のドキュメントはここにありました。
リソースプロパティタイプのリファレンス - AWS CloudFormation
プロパティタイプ定義は例えば下のような構造になっています。
... "AWS::IAM::User.Policy": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html", "Properties": { "PolicyDocument": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html#cfn-iam-policies-policydocument", "PrimitiveType": "Json", "Required": true, "UpdateType": "Mutable" }, "PolicyName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html#cfn-iam-policies-policyname", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" } } }, ...
キーが "リソース名.プロパティ名"
になっています。
内容はドキュメントのリンクとプロパティ一覧で、おそらくプロパティタイプのプロパティは基本の型か Map か Tag に限られています。
Required
はプロパティが必須なものかどうかです。
UpdateType
はリソース更新で変更できるかどうかという意味だと思います。Immutable
, Mutable
, Conditional
のどれかが使われていました。
リソースタイプ定義
リソースタイプ のドキュメントはこれです。
AWS リソースプロパティタイプのリファレンス - AWS CloudFormation
リソースタイプ定義の例です。
... "AWS::IAM::User": { "Attributes": { "Arn": { "PrimitiveType": "String" } }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html", "Properties": { "Groups": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-groups", "DuplicatesAllowed": true, "PrimitiveItemType": "String", "Required": false, "Type": "List", "UpdateType": "Mutable" }, "LoginProfile": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-loginprofile", "Required": false, "Type": "LoginProfile", "UpdateType": "Mutable" }, "ManagedPolicyArns": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-managepolicyarns", "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", "UpdateType": "Mutable" }, "Path": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-path", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "Policies": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-policies", "DuplicatesAllowed": true, "ItemType": "Policy", "Required": false, "Type": "List", "UpdateType": "Mutable" }, "UserName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html#cfn-iam-user-username", "PrimitiveType": "String", "Required": false, "UpdateType": "Immutable" } } } ...
プロパティタイプの定義とほぼ似た感じですがドキュメントリンクとプロパティ一覧以外に Attributes
があります。
Attributes
はリソース作成した時に付与される情報だと思います。 Attributes
に使われるのは基本の型のみで Required
と UpdateType
の指定はありません。
リソースタイプのプロパティは基本の型かプロパティタイプ定義で定義した型が使われています。
コード生成してみる
実験に使ったコードはこれです。
リソース定義を http-client で取得して JSON をパースし、テンプレートエンジンでコード生成という感じです。
仕様からコード生成するという流れはよくあるもののようで例えば Swagger や Protocol Buffers などでコード生成するというのを聞いたことがあります。 どちらもまだ使ったことがないのでこのような場合に使えるかわからないですが、また機会があればこの辺のツールも触ってみたいです。
生成したコードは下のような感じになりました。
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module AWS.IAM.User ( Policy(..) , LoginProfile(..) , User(..) , resourceJSON ) where import AWS (Map, Tag) import Data.Text (Text) import Data.Aeson ((.=)) import qualified Data.Aeson as DA (FromJSON(..), ToJSON(..), Options(..), Value, defaultOptions, object) import qualified Data.Aeson.TH as DA (deriveJSON) data Policy = Policy { _PolicyPolicyDocument :: DA.Value , _PolicyPolicyName :: Text } deriving (Show, Eq) data LoginProfile = LoginProfile { _LoginProfilePassword :: Text , _LoginProfilePasswordResetRequired :: Maybe Bool } deriving (Show, Eq) data User = User { _UserGroups :: Maybe [Text] , _UserPath :: Maybe Text , _UserLoginProfile :: Maybe LoginProfile , _UserUserName :: Maybe Text , _UserManagedPolicyArns :: Maybe [Text] , _UserPolicies :: Maybe [Policy] } deriving (Show, Eq) $(DA.deriveJSON DA.defaultOptions { DA.fieldLabelModifier = drop 7 } ''Policy) $(DA.deriveJSON DA.defaultOptions { DA.fieldLabelModifier = drop 13 } ''LoginProfile) $(DA.deriveJSON DA.defaultOptions { DA.fieldLabelModifier = drop 5 } ''User) resourceJSON :: User -> DA.Value resourceJSON a = DA.object [ "Type" .= ("AWS::IAM::User" :: Text), "Properties" .= a ]
生成したコードを使って一応は Haskell のデータでリソースを定義して JSON を出力するということはできるんじゃないかと思います。 (実際に使うつもりはあまりなく実験してみたという感じです。)
まとめ
CloudFormation のリソース定義をつかってのコード生成をやってみました。 やろうと思えば色々なツールを作ったりもできるんじゃないかと思います。 仕様がプログラムから利用しやすい形で公開されているのはいいなと思いました。
この他にも IAM のアクションの仕様なども公開されるとうれしいと思います。 こちらのページで IAM アクションが検索できるサービスが公開されていました。
これは非公式なページで AWS ドキュメントから情報を抜き出したりして更新しているようです。 IAM アクション仕様が提供されていればこうしたツールも作りやすくなると思います。
InfluxDB と Grafana で可視化して Puppeteer で PDF を生成
はじめに
計測データのグラフや表をまとめたいことが 何度かあったんですがちょうどいいツールを見つけられていませんでした。
エクセルでグラフを作ったりgnuplotでグラフを作ったりもできますが 見た目をかっこよくまとめるのは慣れないと難しそうに感じました。
Grafana は前に目にしたことがあったんですが 常時計測したデータの可視化用でちょっと使いたい用途には合わないかなと思ってました。
ただ docker-compose を使って CLI ツールのようにまとめることで やりたい用途にも使えそうだなと思い試してみました。
実験に使ったリポジトリはここに置いています。
やり方
InfluxDB, Grafana, Puppeteer のコンテナを docker-compose で連携させるというやり方です。 なんでも docker でやってみたくなっている感じが少しあります。 色々なツールを使っていて全部直接入れるのはちょっと気が引けるし、環境構築も大変なので実際こういう用途では便利だと思います。
InfluxDB
InfluxDB は時系列データを扱うデータベースで Go 言語で書かれているようです。 Grafana とセットでよく使われているのを目にする気がします。 データの有効期限を設定できるようで常時計測しているデータの保存などに使うことが多いようです。
InfluxDB Line Protocol reference | InfluxData Documentation
Writing data with the HTTP API | InfluxData Documentation
Line Protocol の書き方と curl での API 呼び出し方の例を真似してデータを取り込むことができました。
公式のイメージが Docker Hub にありました。
https://hub.docker.com/r/library/influxdb/
Grafana
Grafana はデータの可視化用のウェブサーバーで GUI でダッシュボードに色々なグラフを表示したりできます。 InfluxDB 以外にも色々なデータソースを扱うことができるようです。 Grafana もサーバー部分は Go 言語のようです。
Grafana も Docker Hub にイメージがありました。
https://hub.docker.com/r/grafana/grafana/
Puppeteer
Puppeteer は Chrome の開発用 API を利用できる Node ライブラリです。
ウェブページの PDF を作るのに他のツールも少し使ってみたのですが
表示が終わるのを待つなど細かい制御をするには Puppeteer を使ったほうがうまく行きそうだったので使ってみることにしました。
下のような感じで PDF の保存ができました。
const puppeteer = require('puppeteer'); const url = process.argv[2]; const output_path = process.argv[3]; const width = 1080; const height = 1920; (async() => { const browser = await puppeteer.launch({ headless: true, executablePath: 'chromium-browser', args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const page = await browser.newPage(); await page.setExtraHTTPHeaders({ Authorization: 'Basic ' + new Buffer('admin:admin').toString('base64') }); await page.setViewport({ width: width, height: height, isMobile: false }); await page.goto(url, {waitUntil: 'networkidle2'}); await page.pdf({ path: output_path, width: width + 'px', height: height + 'px', }); await browser.close(); })();
puppeteer/troubleshooting.md at master · GoogleChrome/puppeteer · GitHub
ここの Dockerfile の書き方を参考にイメージを作りました。
docker-compose 設定
docker-compose 設定は下のようになりました。
Grafana のダッシュボードとデータソースの設定は
/etc/grafana/provisioning/
以下に設定ファイルを置くと反映されるので
設定ファイルを置いたディレクトリをマウントするようにしています。
version: "3" services: influxdb: image: influxdb:1.6.2 ports: - "8086:8086" grafana: image: grafana/grafana:5.2.4 ports: - "3000:3000" volumes: - ./grafana/dashboards:/etc/grafana/provisioning/dashboards - ./grafana/datasources:/etc/grafana/provisioning/datasources depends_on: - influxdb puppeteer: build: context: . dockerfile: Dockerfile.puppeteer volumes: - ./volume:/volume entrypoint: - ./run-puppeteer.sh depends_on: - grafana - influxdb
生成した PDF
一連の流れを試してみるのが目的だったので見栄えしないグラフ一つしかありませんが下のような感じで生成できました。
Grafana のダッシュボードは kiosk=true
の GET パラメーターを付けるとメニューを非表示にできるようです。
やろうと思えば Puppeteer で少し見た目を加工するなどもできるのかなと思います。
まとめ
InfluxDB にデータを取り込み Grafana で表示して Puppeteer で PDF 作成という一連の流れをdocker-compose で CLI 化することができました。 はじめて使うものが多かったので少し時間がかかりましたが今後も使う機会がありそうなので少しは慣れることができてよかったと思います。
Puppeteer で PDF 生成する前に Grafana のサーバーが立ち上がるのを少し待つ必要があるのを今回は10秒スリープするといういい加減な方法で対応しました。 こういう用途には wait-for-it や dockerise が使えるらしいです。
Docker 内で GLFW のプログラムを動かすサンプル
はじめに
Docker の利用は CLI やサーバーサイドの利用が主なのかなと思っていましたが GUI のプログラムもある程度は動かせるようです。
軽いゲームなどを作る場合も使えるかもしれません。
実験用に GLFW のデモアプリを動かす Docker イメージを作ってみました。
Dockerfile などはここのリポジトリに置きました。
マウントしたり特権コンテナにしたりという設定があるため docker-compose を使っています。
Docker での GUI
以下のページなどを参考にさせてもらいました。
docker/Tutorials/GUI - ROS Wiki
https://unskilled.site/dockerコンテナの中でguiアプリケーションを起動させる/
https://tail-island.github.io/programming/2017/07/11/docker-for-development-container-on-linux.htmltail-island.github.io
いくつか方法があるようです。
- X11 ソケットを共有する
- ホストの X サーバーとコンテナの X クライアントがソケット通信
- コンテナで X サーバーを動かす
- コンテナで X サーバーも X クライアントも動かす
- ssh を使った X11 forwarding
- VNC を使う
ホスト側も X ウィンドウシステムの場合は上の二つの方法が使えそうです。
自分ではホストも Ubuntu などで動かすと思うので一番簡単そうな X11 ソケットを共有する方法を試してみました。
Docker イメージ
前に作った haskell 用のイメージを元にして作っています。
開発用として使う想定なので他の言語ではそれぞれの開発環境が入ったイメージを元にして必要なパッケージをいれることになると思います。
FROM tkaaad97/haskell-docker:8.2.2 # install dev tools RUN apt-get update && \ apt-get install -y --no-install-recommends \ make \ pkgconf \ xz-utils \ xorg-dev \ libgl1-mesa-dev \ libglu1-mesa-dev \ libxrandr-dev \ libxinerama-dev \ libxcursor-dev \ libxi-dev \ libxxf86vm-dev WORKDIR /app/ ENTRYPOINT [] CMD ["bash"]
下は docker-compose 設定ファイルです。
/tmp/.X11-unix:/tmp/.X11-unix:rw
の部分で X11 ソケットをマウントして共有しています。
command で GLFW-b-demo のパッケージをインストールして起動しています。
version: "2" services: app: build: . command: bash -c 'stack install GLFW-b-demo && /root/.local/bin/GLFW-b-demo' working_dir: /app privileged: true environment: - DISPLAY=${DISPLAY} volumes: - .:/app - .stack:/root/.stack - /tmp/.X11-unix:/tmp/.X11-unix:rw
xhost
コンテナとホストで X11 ソケット共有して通信する必要があるのですが コンテナ上のユーザーとホスト側で接続しているユーザーが 違う場合にそのままでは通信が許可されません。 この場合に xhost を使って許可することができます。
xhost local:
これを実行するとローカルの接続であればユーザーが違っても許可されます。
ただしあまり安全な方法では無いらしくコンテナのユーザーとホストのユーザーを合わせる方法なども紹介されていました。
スクリーンショット
以下のようにデモアプリが表示されました。