OpenGL を使うテストを CI でするときに osmesa が使えそう

はじめに

前に docker で OpenGL を使う環境を作ってそれでローカル環境では動かせていたんですが CI の環境では GPU が使えなかったり、X Server がない、privilegedのオプションが使えないなど場合もあって そのままでは使えないことが多いので CI でも使える方法を少し調べていました。

テストするときも実際に使うときと同じ環境でやったほうがいいのは間違いないですが、 CI である程度の部分だけでもテストできると嬉しいと思います。

osmesa

Linux 環境では OpenGL はほとんどの場合 GLX から使うようになっています。 同様に Windows の場合は WGL から OpenGL を使います。

GLW と WGL には OpenGL のコンテクストを作成したり、OpenGLAPI のアドレスを取得するインターフェースがあります。

Programming OpenGL in Linux: GLX and Xlib - OpenGL Wiki

CI 環境や GUI がインストールされていないサーバーだと GLX が使えない場合があります。 こうした場合に osmesa が使えるようです。osmesa はソフトウェアレンダリングによる OpenGL 実装です。

www.mesa3d.org

ただ osmesa は OpenGL の機能を完全に実装しているわけではないようで、新し目の glsl バージョンのシェーダをコンパイルできないことがありました。 なので描画に直接関係する部分のテストには使えないかなと思いますが、バッファの確保や解放などリソース管理のテストぐらいはある程度できると思います。

Ubuntu の場合は apt で osmesa を入れることができたので、これを使っています。 (apt のパッケージは libosmesa6-dev という名前でした。)

自分でビルドするともう少し新しい OpenGL バージョンに対応しているかもしれません。

LD_PRELOAD で関数を置き換える

osmesa を使うにはコンテキストを初期化する部分と OpenGL API のアドレスを取得する部分を変更する必要があります。

コンテキストを初期化する部分は独自に実装すればなんとかなりますが、 OpenGL API のアドレスを取得する部分が OpenGL バインディングライブラリ内にあって切り替えられないという問題がありました。

そこで LD_PRELOAD の環境変数を使って glXGetProcAddress を呼び出したときに OSMesaGetProcAddress が使われるようにするという方法を使いました。

動的リンクのライブラリに同じ名前の関数が複数あった場合に先に見つかった関数が使われるため、 LD_PRELOAD でライブラリを読み込ませることで上書きできるようです。

下のようなコードを書いて共有ライブラリを作成して使いました。

typedef unsigned char GLubyte;
typedef void (*genericFunctionPointer)(void);
extern genericFunctionPointer OSMesaGetProcAddress(const GLubyte *);

genericFunctionPointer glXGetProcAddress(const GLubyte* name)
{
    return OSMesaGetProcAddress((const char*)name);
}

genericFunctionPointer glXGetProcAddressARB(const GLubyte* name)
{
    return OSMesaGetProcAddress((const char*)name);
}
gcc -shared -fPIC cbits/glxoverride.c -o libglxoverride.so

GPU を使える場合

github.com

nvidia で公開されている docker コンテナでは GLX のかわりに EGL を使うようになっているようです。 GPU は使えるが X Server はないという場合は EGL が使えそうです。

CircleCI で GPU を使える環境もあるようで予算がある場合はこれもいいかもしれません。

https://circleci.com/docs/ja/2.0/gpu/

CodeDeploy を導入したときに調べたことなど

はじめに

大分前に CodeDeploy について少し調べたことがありました。

tkaaad97.hatenablog.com

その後実際に CodeDeploy を導入する機会があってもう少し色々調べたり試したりしたことがあったのですが、導入したことに満足して調べたことをまとめていませんでした。

しばらく時間がたって忘れてるところもありますが、また使う機会もあるかもしれないのでここに書いておきます。

リビジョン作成

CodeDeploy ではデプロイするソースとして S3 か GitHub が使えます。 しかしデプロイ時に展開するものは GitHub で管理されているソースだけでは無いことが多いので S3 を使うことの方が多いと思います。 スクリプト言語の場合は依存ライブラリなども取得してデプロイする必要があります。 コンパイルする場合バイナリは Git 管理しないことが多いと思います。

S3 にファイルをアップロードしてリビジョンを作成するためのコマンドがあります。

docs.aws.amazon.com

しかしこのコマンドではあまり細かい制御ができないため結局使いませんでした。 隠しファイルを含めるかどうかというオプションがあるのですが一部だけ含めたりという指定ができませんでした。

dev.classmethod.jp

こちらの記事を参考に aws deploy push に相当する処理を行うスクリプトを作成してリビジョン作成を行うようにしました。

場合によっては CodeDeploy でのインストール処理とは別に Ansible Playbook などを実行して独自にインストール処理を行うということもあるかもしれません。 この場合は CodeDeploy でインストールするのは Ansible のファイルだったりリビジョンに含めるものは変わってくると思います。

ソース配置ディレクトリをアプリケーションによって変えたい

CodeDeploy でどのディレクトリにソースを配置するかは appspec.yml で指定します。 files の destination の部分です。

docs.aws.amazon.com

あまり普通はやらないし推奨されないと思いますが 複数のアプリケーションを同一のサーバーにデプロイする場合 ソース配置ディレクトリが同じになってしまいます。

アプリケーションごとに appspec.yml を変えるといった方法は無いようでした。 しかしリビジョン作成時に appspec.yml を書き換えるという無理やりな方法で一応は目的のことができました。

デプロイグループごとにデプロイ処理を変えたい

同じソースをデプロイするサーバーでも色々と役割が違うものがあります。 例えばウェブサーバーがあったり、分析や集計などを行うサーバーがあったりして、 役割ごとにデプロイグループを分けるのが普通だと思います。

デプロイグループごとに変えるには CodeDeploy で提供されている環境変数を使うことができます。

docs.aws.amazon.com

APPLICATION_NAME と DEPLOYMENT_GROUP_NAME の環境変数があるので これに基づいて設定ファイルを読み込んだり、AWS Systems Manager パラメータストアから設定を取得し デプロイの処理を変えることができます。

オートスケーリンググループに複数のデプロイグループを付けられるか

複数のデプロイグループに同じオートスケーリンググループを設定することはできるんですが、 試したところこれはやめたほうがいいということが分かりました。

問題はオートスケーリングでインスタンスが新しく追加されたときに 両方のデプロイグループのデプロイ処理が同時に動いて正常に動作しないという点でした。 オートスケーリングを使っていない EC2 タグでデプロイを行っている場合はそんなに問題ないかもしれません。 同時にデプロイしたりするとエラー起こりそうですが。

デプロイごとに処理を変えたい

複数デプロイグループを作ろうとしたのは同じ役割のサーバーでもデプロイを行うときに処理を変えたい場合があったからでした。 先に書いたように複数デプロイグループで分岐するのは上手くいかなかったため、 リビジョン作成時にファイルを追加してこのファイルを読んで処理を分岐するというような方法を使いました。

オートスケーリンググループに複数のロードバランサーが付いている場合

複数のロードバランサーには CodeDeploy は対応していないようです。 複数のロードバランサーを使うのは諦めるか、自前でロードバランサー関連の処理を行うことになります。

オートスケーリンググループのインスタンスは全てをロードバランサーに付けるか外すかの二択かと勘違いしていたのですが、 オートスケーリンググループ中の個々の EC2 インスタンスごとにロードバランサーから一時的に外したりということができます。

docs.aws.amazon.com docs.aws.amazon.com

インプレースデプロイの場合はアプリケーションストップ時にロードバランサーから一時的に外し、 デプロイが終わってアプリケーションスタート時にロードバランサーに付けるという感じのことができるようです。

この処理については GitHub に公開されていたサンプルが参考になりました。

github.com

Blue/Green デプロイの場合はオートスケーリンググループ自体が複製されてインスタンスも全て入れ替わるということになるらしいです。 この場合ターゲットグループやロードバランサーとの紐付けもそのまま引き継がれるのかどうか疑問ですがインプレースデプロイを使うことにしたため詳しく調べていません。

シンボリックリンクが壊れるバグ

github.com github.com

シンボリックリンクが通常のファイルに変わってしまうという問題が起こる場合があるようです。 この挙動で問題が起こる場合はデプロイ処理内でシンボリックリンクを作るようにするなどの対応が必要になりそうです。

導入してみての感想など

少し融通がきかないところがあったり、色々と疑問に思ったところがドキュメントに書かれていなかったりして 全く不満が無いわけではないですが CodeDeploy まあまあ便利だと思います。 CodeDeploy 前に使っていたデプロイ方法が色々と問題があったためそう感じるというところもあると思います。

便利と感じるところはオートスケーリングと一緒に使えて新規インスタンス追加時に色々な処理が行えるところや、 デプロイの履歴やインスタンスごとに起こったエラーなどが簡単に調べられることなどです。

XPath と Lens で XML を扱う

はじめに

OpenGL の仕様が XML で書かれていてこれについて調べたりしてました。 XML 扱う機会があまりなくてほとんど使ったことがなかったんですが調べてみると色々と XML 使われているところはあるのだなと感じました。 XML から検索したりコード生成したりしたくて調べたことをまとめておきます。

XPath

developer.mozilla.org

ja.wikipedia.org

XPathXML Path Language の略です。 XPath を使って XML から必要な情報を検索したりできるようです。

JSON でいうところの JMESPath や jq などに近いものだと思います。

XPath は HTML にも使えるようで JavaScript やクローラでも使われたりする例を見かけました。 XPath 3.1 までバージョンが出てるらしいんですが、新しいものは対応しているツールがあまり無いようでした。

CLI から XPath を扱うことができるツールがあって XMLStarlet や xmllint が使えるようです。

xmlstar.sourceforge.net

xmlsoft.org

xmlstarlet では XML の更新も行える機能があるようなのでこちらを使ってみてます。

問い合わせの場合は sel のサブコマンドを使います。 ドキュメントはこちらです。

xmlstar.sourceforge.net

Lens

XPath を使って CLI で検索したりはできたんですが Haskell などから扱う場合にどうするのかなと調べていたところ Lens を使うと似たようなことができそうでした。

Lens についてはあまりよくわかっていないので詳しくは他の情報を参照してください。 とりあえず使うだけであれば Getter や Setter として使えるというぐらいの理解でもいいような気がします。

XML を扱う Haskell のライブラリはいくつかあるんですがあまり更新されてないものもあってどれを使うか迷いました。 XML 自体の仕様が変わっているわけでは無いので最近更新されていなくてもそんなに問題なく使えるような気もします。

hackage.haskell.org

xml-conduit は比較的更新されていそうだったためこれを使ってみています。

hackage.haskell.org

xml-conduit の Lens ライブラリとして xml-lens がありました。

hackage.haskell.org

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 をそんなに使いたいというわけではないのですが、すでに作ったシナリオを利用したいなど使うことはあるかなと思います。

aws.amazon.com

ECS は去年少し触ってみてからあまり使う機会がなくてすでに EKS も使えるようになっていました。

aws.amazon.com

Kubernetes を負荷テストに使うという記事も見かけたことがあるので、Kubernetes 使う方が GCP などでも活用しやすいかもしれません。

cloud.google.com

今回は ECS まだあまりつかったことがなくてもう少し使い方を覚えたいということで ECS でやってみてます。 それから JMeter 以外のツールもこれから使うと思うのでいくらか触れています。

今回使ったテンプレートや Dockerfile などは下のリポジトリにおいています。

github.com

分散負荷テストのための構成

負荷テストツールでは大きな負荷をかけられるようにするため多くは分散テスト (複数のサーバーから負荷をかける) ができるようになっています。 ツールによってこの仕組みが色々違っているので使うものに合わせて実行環境を用意する必要があります。

JMeter

JMeter のウェブページはここです。

jmeter.apache.org

JMeter を動かすときの構成を図にしてみました。

f:id:tkaaad97:20190127193538p:plain

JMeter に外からアクセスする必要はないためプライベートサブネット内にクラスターをおいています。 図に書いてないですが ECR からのイメージダウンロードや負荷テスト時にはインターネットアクセスは必要なので NAT を使っています。 図ではマスターも同じ ECS クラスターで実行していますが、 マスターは手動でコマンドを色々変えて実行したいということもあると思います。 マスターだけパブリックサブネットに踏み台のような EC2 サーバーを用意してそこから実行するというやり方もあると思います。

JMeter ではまずスレーブをサーバーモードで立ち上げて マスターを実行するときにスレーブのIPアドレスとポートの一覧を指定して接続できるようにするという流れになります。 また JMeter の場合はマスターのプロセスを実行したときに負荷テストもスタートして計測結果がマスターに集められログのファイルに保存されます。

JMeter の場合は

  • スレーブの IP とポートの一覧を取得できるようにする
  • マスター実行時に結果をログに出力するか S3 などにアップロードする
  • マスター・スレーブ間で通信できるようにポートなど設定する

ということが必要そうです。

ECS上のタスクのIPとポートは ecs-cli から取得することができるようです。 色々取得方法があると思いますがサービスディスカバリに登録するようにして aws-cli から取得するということもできました。

github.com

上のリポジトリVPC、サブネット、NAT、ECS クラスター、オートスケーリンググループなどの CFn テンプレートの例が公開されていました。 これを真似して一通り構築することができました。

Locust

Locust のページはこちらです。

locust.io

Locust ではマスターを先に立ち上げ、スレーブの起動時にマスターのIPとポートを指定して接続することになります。 Locust のマスターはウェブサーバーとしても機能してウェブページから負荷テストをスタートさせられます。 負荷テスト結果もウェブページから見ることができます。

ECS を使った例を色々と探していて Locust の記事がいくつか見つかったため参考にさせてもらいました。

dev.classmethod.jp

Locust と ECS を使った負荷テストについてこちらの記事で詳しく解説されていて参考になりました。CFn テンプレートも記載されています。

マスターへのインターネットからアクセスが可能なようにマスターのみ Fargate モードでパブリック IP を付けて動作させているようです。

マスターとスレーブはそれぞれタスク定義を分けて別のサービスにしているようです。

現在ではサービスディスカバリも利用できるようになっているのでスレーブからマスターへのアクセスは サービスディスカバリで行うこともできると思います。

inokara.hateblo.jp

こちらの記事も同じく ECS と Locust を使った例です。

こちらの記事では docker-compose.yml でマスター・スレーブ構成を定義して使うようにしていました。

ecs-cli では docker-compose.yml の定義をタスクとして実行するという機能があるようです。

docs.aws.amazon.com

docker-compose を使うことができるとローカルでも同じ構成で確認できて便利だと思います。

ただし一つのタスク定義内に入れられるコンテナ数は10以下に制限されるという点には注意する必要があると思います。

大きな負荷が必要な場合にはタスク定義を分けておいたほうがいいと思います。

Fargate と EC2 どちらを使うか

構成は Fargate の方が簡単になるとは思います。 アカウント毎の Fargate タスク数の制限が前はデフォルト20まででしたが今は50になっていて少し使いやすくなっています。 料金も値下げされたようです。

ただ EC2 で動かす場合もクラスター構築の CFn テンプレートなどを用意しておけばある程度簡単にセットアップできると思います。 また EC2 の場合はスポットインスタンスを使って安く使うことができる場合があります。 状況によってどちらがいいかというのは変わりそうなので EC2 での手順も用意しておいて Fargate で簡単に対応できる要件であれば Fargate でやるなど選択することになるかなと思います。

今回は手順を色々覚えようということで EC2 で試しました。

Dockerfile

JMeter を動かすため Docker イメージには JRE が必要になります。 OpenJDK のイメージが公開されていたのでこれを元にその他必要なものを入れました。 Java のイメージの作り方に詳しくないのでもっとイメージサイズを小さくする方法があるのかもしれません。

hub.docker.com

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.amazon.com

AWS S3 への匿名での Put

概要

AWS S3 に匿名で Put すると危ないという話についてです。

私もこれを間違ってやってしまってました。

AWS のドキュメントにも少し書かれていましたが間違って使ってしまっている人もいるんじゃないかと思います。

docs.aws.amazon.com

注記

バケットをパブリックにした場合 (非推奨)、認証されていないどのユーザーもバケットにオブジェクトをアップロードできます。これらの匿名ユーザーは 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のアクセスコントロールについて参考

docs.aws.amazon.com

dev.classmethod.jp

Android で朝起きるときに音楽を流したい

概要

スマート家電などを活用している人の話に影響されて 朝起きる時に音楽を流したり照明を自動で点けたりしたいと思い色々試してました。

スマートスピーカーがあるとアラームとして音楽を流すなど簡単にできそうですが スマートスピーカーは持ってないため手持ちの Android タブレットBluetooth スピーカーでやる方法を試しました。 Automate でアラームをトリガーにして音楽を流すというやり方で目的のことができました。

アラームアプリ

最初から入っていたアラームのアプリや Google が出している時計のアプリもあってこれで十分の場合もあると思います。

play.google.com

Spotify のアプリと連携してプレイリストを再生するなどもできるようです。

ただし最初から入っていたアラームアプリだと外部スピーカーに音声出力するという設定ができませんでした。 またできれば Amazon Music も使えるようにしたいですが今のところはできないと思います。

IFTTT で Android Device 連携 (上手くいかなかった)

ifttt.com

IFTTT (If this, then that) というサービスがあってスマート家電などと一緒によく使われているようです。 トリガーとなるサービスと、そのときに実行するアクションを組み合わせて色々なことができるようになっています。

ifttt.com

Android 向けのアクションとして Play music もありました。 時間をトリガーにしてこのアクションを使えば目的のことができそうだったんですが 機種の問題なのかわかりませんが上手く動きませんでした。

上手くいかなかったのは Android を長時間スリープ状態にしておくと トリガーを設定した時間になってもスリープ解除されず音楽も再生されないというところです。 トリガー時間をすぎて手動で電源を入れるとその時点から音楽が流れますが これでは目覚ましになりません。

それからどの音楽アプリを使うかというのが設定しにくくなっています。 最初にトリガーされたときにどの音楽アプリを使うか選択する画面がでてきますが これを毎回選択するようにすると目覚ましにはできませんし、固定にするとあとで変えたりできなくなります。 音楽アプリを新しく追加すると選択画面がまた出るようになりますが、 このせいで選択画面で止まってしまい次の朝に目覚ましとして動かないという感じでやはり使いづらいです。

Automate

play.google.com

Automate ⋅ everyday automation for Android ⋅ LlamaLab

Automate は Android 上での色々なタスクの自動化などに使えるアプリです。 IFTTT より複雑で一から使い方を覚えるのは大変そうですが 他のユーザーが作成したフローを真似して作れば一応は使えそうです。

Alarm ⋅ Automate ⋅ LlamaLab

Automate にはアラームをトリガーにするブロックがありました。 標準のアラームではスリープから復帰できていたので、アラームと同時に起動するようにすれば IFTTT のときのようにスリープ抜けないという問題は回避できるのではないかと思いました。

Amazon Music で検索するといくつかフローがあったため音楽を流すところを参考にして トリガー部分をアラームにしてフロー作成しました。フローは下のようになりました。

f:id:tkaaad97:20181111144250p:plain

アラーム側は無音でバイブレーションもなしで画面だけ明るくなるという設定にして動作させています。

まだ使って一週間ほどですが大体問題なく動いています。 (スリープのせいか起動が少し遅れたりはしますが)

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 のテンプレートは YAMLJSON で書くことができますがなかなか大変です。 少し複雑なものを作ろうとするとリファレンスを参照しながら、トライアルアンドエラーでスタックを何度も立てたりして書いていた記憶があります。 色々な言語で IDE の補間やシンタックスチェックなどが効いた状態で書ければ便利になりそうです。 またプログラミング言語で書けるので、他のリソース情報を参照したりということもやりやすくなるのではないかと思います。

すこし調べていたところ CloudFormation のリソース仕様が公開されていると知りました。

docs.aws.amazon.com

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 があるようです。 このほかに MapTagPrimitiveType ではないですが定義なしで使われていて少し特殊な扱いのようでした。

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 に使われるのは基本の型のみで RequiredUpdateType の指定はありません。 リソースタイプのプロパティは基本の型かプロパティタイプ定義で定義した型が使われています。

コード生成してみる

実験に使ったコードはこれです。

github.com

リソース定義を 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 アクションが検索できるサービスが公開されていました。

Complete AWS IAM Reference

これは非公式なページで AWS ドキュメントから情報を抜き出したりして更新しているようです。 IAM アクション仕様が提供されていればこうしたツールも作りやすくなると思います。