この記事はscouty Advent Calendar 2018 の3日目です.
ECSで動かしているサービスのスケジュールジョブが多くなってきた. もともとECS Scheduled Taskを使っていたんだけど,数十個レベルになってくると,これで管理するのはだいぶつらい.
というわけでRundeckを構築したメモ.
そもそもECS Scheduled Taskやる気あんの?
ECS Scheduled Taskの辛いところ.
- Scheduled Taskは既存のTaskDefinitionを上書きしてタスクを実行するため,Override用にjsonでTaskDefinitionの一部を与える必要がある
- Scheduled Taskの一覧画面はあるのだが,そこから「何時に,どのタスクIDで実行されたか」を知る術がない
- そんなだから,タスクが正常に終了したのか,何かのエラーで途中終了したのかもわからない
- そして,Scheduled Taskの画面上からは実行ログを辿ることもできないので,もはや何もわからない
という事情があるため,真面目にログをトレースしようと思うと,TaskDefinitionをOverrideするときに,logDriverの設定を上書きして,タスク固有の識別子を埋めておくしかない.
この状態で数十個単位のジョブを管理するなんて不可能じゃないか? AWSは本当にこんなものでスケジュールジョブを管理できると思ってるんだろうか?
単にKubernetesのcronに対抗したくて既存の仕組みの上で作っただけなのでは?という気がしないでもない. 実際CloudWatchEventsのtargetにrun taskが指定できるようになっただけだし.
というわけでもう諦めてRundeckを立てることにしたのであった.
Rundeck目指す構成
Rundeck自体もECSに乗せてやりたい.そして,ジョブの実行はECS run-task API経由で行うものとしたい. そうしておけば,ssh等の設定は一切必要なく,単にRundeckを動かしているインスタンス上のローカルコマンドでecs run-taskができれば良い.そうしておけばssh周りの設定,VPCや,SecurityGroupや,ssh鍵の置き場所などを悩む必要がない.
ただし,run-task APIを,同期的な実行として,ログ出力を標準出力に行う必要がある.ログはRundeck上で見られるようにしたいし,コマンドの実行結果もSlack通知とかしたいじゃん? これに関しては先日書いた,
ecs-taskをそのまま使えば実現できるので,今回はあまり触れない.
というわけで大事なこと.
という要求を満たすRundeckを作ることにする.
RundeckをDockerで立てる
公式がRundeckのDockerを用意してくれているので,これを使えばよい.ただし,環境変数でかなりのことをカスタマイズできるようになっているので,今回はそこの説明をする.
https://hub.docker.com/r/rundeck/rundeck/
前提
公式のRundeck Docker imageは,設定の多くを環境変数で変更することができる. ただし,Rundeck自身は,環境変数を直接読むのではなく設定ファイルを読み込んでいる.
そのため,remcoというツールを使って,entrypoint内で,環境変数を元に設定ファイルを生成している.
https://github.com/rundeck/rundeck/blob/v3.0.8/docker/official/lib/entry.sh#L17
remcoというのはこれ.
というわけで,今から説明する設定は,だいたいremcoに食わせる環境変数を設定するのが主となる.
SSL化
今回はAWS ECS上に構築することもあって,SSL証明書の管理はACMで行い,SSLの終端はALBとする.
ただし,SSLの終端がALBであっても,多少の設定が必要になる.
それがこれ.
https://rundeck.org/docs/administration/security/configuring-ssl.html#using-an-ssl-terminated-proxy
で,この設定は環境変数で切り替えられる. こいつはremcoではなく,実はentrypoint内で環境変数から読み込みを行っている.
https://github.com/rundeck/rundeck/blob/v3.0.8/docker/official/lib/entry.sh#L34
というわけなので,
export RUNDECK_SERVER_FORWARDED=true
と環境変数を与えてやれば良い.
あと,Rundeck内で利用するURLも与えてやる必要があるのだが,これもSSL化しておく.
export RUNDECK_GRAILS_UR=https://rundeck.example.com
ついでに以下の環境変数も設定しておくと,外部から接続できるようになる.
export RUNDECK_SERVER_ADDRESS=0.0.0.0
これらは,
この辺で読み込まれ,remcoによって設定ファイルに書き出される.
Storage
次にStorageの設定だ. RundeckのDBはデフォルトではh2を使う設定になっており,これはローカルにファイルとして書き出される. そのため,Rundeck上で作成するプロジェクトの設定等もすべてローカルに保存される. クラスタ構成にする上でこれをRDS等のリモートのRDBに変更してやる必要がある.
とはいえ,普通にDB設定の環境変数を設定すれば良い.
https://hub.docker.com/r/rundeck/rundeck/
export RUNDECK_DATABASE_URL=postgres_url export RUNDECK_DATABASE_DRIVER=org.postgresql.Driver export RUNDECK_DATABASE_USERNAME=rundeck export RUNDECK_DATABASE_PASSWORD=hogehoge
これはpostgresの設定だが,mysqlでも同じような設定だけで行ける.
ExecutionFileStorage
Rundeckはcronなので何かしらのコマンドを実行する.そしてその実行ログ(ExecutionLogFile)をRundeck上から確認できるのもメリットだ.
デフォルトの場合,その実行ログもローカルファイルに書き出されてしまう. これもクラスタ構成にするために,ディスクではなくS3あたりに書き出しておく必要がある.
これに関してはこの辺が参考になる.
まず,rundeck-s3-log-pluginを入れる必要がある.
これは,rundeckがインストールされたディレクトリのlibext
配下にプラグインを置くことで自動的に読み込まれる.
つまり,こんな感じのDockerfileを書く必要がある.
FROM rundeck/rundeck:3.0.8 USER root RUN set -ex && \ mkdir libext && cd libext && \ wget https://github.com/rundeck-plugins/rundeck-s3-log-plugin/releases/download/v1.0.5/rundeck-s3-log-plugin-1.0.5.jar && \ chown -R rundeck:rundeck /home/rundeck/libext && \ USER rundeck
次にこのプラグインを使うように本体側に設定をしてやる必要がある.
この設定も環境変数のみでコントロールできる.読み込んでいるのはここ.
# execution logをS3に保存するための設定 export RUNDECK_PLUGIN_EXECUTIONFILESTORAGE_NAME=org.rundeck.amazon-s3 export RUNDECK_PLUGIN_EXECUTIONFILESTORAGE_S3_BUCKET=my_s3_bucket export RUNDECK_PLUGIN_EXECUTIONFILESTORAGE_S3_REGION=ap-northeast-1
これで,ExecutionLogFileがS3に吐き出されるようになる. 適当なジョブを実行してみると,ちゃんとS3にログが出てくる.
GitHub認証
Rundeckは,ユーザ認証に,ユーザ名+パスワードを要求してくる.しかし,この設定は実はファイルに書かれて管理されているだけだ.こうなると,新しいメンバーが入ってきた時や,メンバーが抜けたときにいちいちユーザ管理ファイルを書き換える必要がある. また,パスワード漏洩のリスクも増える.
そいういうのを考慮して,できればGitHubやGoogle認証に変えたい.
というわけで
この辺を参考にGitHub認証を入れてみた.
この情報は少し古くなっており,Rundeck 2.x系であれば問題ないのだが,3.x系だと少し設定が変わっている.
記事の通りRundeck自身はGitHub認証の機構を持たない.そのために,前段にoauth2_proxyを入れて,oauth2_proxyで認証したものをそのままRundeck側に渡す構成を取る.
Preauthenticated Mode
まず,元来Rundeck側で行っている認証をoauth2_proxyに任せるため,Rundeck側の認証を無効化しつつ,ユーザ情報をヘッダーから取れるようにする必要がある. それがこのPreauthenticateModeなのだが,もちろんこの設定も環境変数から行うことができる.
設定したい項目はこれ.
https://rundeck.org/docs/administration/security/authenticating-users.html#preauthenticated-mode
で,この設定ファイルを生成しているのがここ.
というわけで該当の環境変数を出力する.
export RUNDECK_PREAUTH_ENABLED=true export RUNDECK_PREAUTH_USERNAME_HEADER=X-Forwarded-User export RUNDECK_PREAUTH_REDIRECT_LOGOUT=true
で,参考記事の場合,このあとweb.xml
をいじって既存認証を無効化しているのだが,3.x系では web.xml
は存在しない.
Preauthenticated Modeの設定させしてあれば,web.xml
の変更がなくても動くという情報があり,このままでもうまくいったので,ここをスルーする.
ただし,ここで設定している X-Forwarded-Roles
だけはoauth2_proxyが付与してくれないヘッダーなので,自分でnginx等を前段に置いてヘッダーを付与する必要がある.
nginxを設定する
証明書はACMで管理しているし,本当は必要ないはずだったんだけど,ALBだけで新しいヘッダーを付与するのがどうにも不可能っぽかったので(もしできるなら誰か教えてほしい),仕方なくnginxを建てた.
server { listen 80 default_server; server_name localhost; set_real_ip_from 172.0.0.0/8; real_ip_header X-Forwarded-For; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Set rundeck role proxy_set_header X-Forwarded-Roles admin; location / { proxy_pass http://oauth2_proxy:4180; } }
oauth2_proxyの設定
これを動かすだけのDocker imageを作った.
FROM alpine:3.8 RUN apk --no-cache add curl RUN curl -sL -o oauth2_proxy.tar.gz \ "https://github.com/bitly/oauth2_proxy/releases/download/v2.2/oauth2_proxy-2.2.0.linux-amd64.go1.8.1.tar.gz" \ && tar xzvf oauth2_proxy.tar.gz \ && mv oauth2_proxy-2.2.0.linux-amd64.go1.8.1/oauth2_proxy /bin/ \ && chmod +x /bin/oauth2_proxy \ && rm -r oauth2_proxy* CMD /bin/oauth2_proxy \ -provider="github" \ -github-org="${OAUTH2_PROXY_GITHUB_ORG}" \ -github-team="${OAUTH2_PROXY_GITHUB_TEAM}" \ -http-address="0.0.0.0:4180" \ -redirect-url="${OAUTH2_PROXY_REDIREC_URL}" \ -upstream="http://rundeck:4440/" \ -email-domain="*" \ -cookie-domain="${OAUTH2_PROXY_COOKIE_DOMAIN}" \ -cookie-refresh="${OAUTH2_PROXY_COOKIE_REFRESH}"
GitHub関連の情報は起動時に環境変数で差し込めるようにしてある.
これで,ALB -> nginx -> oauth2_proxy -> Rundeck
という流れができて,無事GitHub認証できるようになった.
Slack通知
最後に,ジョブの実行終了のタイミングでSlack通知したいと思う. これはRundeck公式では用意してくれていないのだが,
こいつを使う.
これも追加プラグインなので,Dockerfileでプラグインを追加する.
FROM rundeck/rundeck:3.0.8 USER root RUN set -ex && \ mkdir libext && cd libext && \ # S3 log pluginを用意 wget https://github.com/rundeck-plugins/rundeck-s3-log-plugin/releases/download/v1.0.5/rundeck-s3-log-plugin-1.0.5.jar && \ # Slack pluginを用意 wget https://github.com/higanworks/rundeck-slack-incoming-webhook-plugin/releases/download/v0.9.dev/rundeck-slack-incoming-webhook-plugin-0.9.jar && \ chown -R rundeck:rundeck /home/rundeck/libext && \ # 以下のディレクトリはSlack pluginがテンプレートを吐き出すために必要 mkdir /etc/rundeck && \ chown -R rundeck:rundeck /etc/rundeck USER rundeck
先にSlack側でIncoming Webhooks用のURLを出しておく.
そして,Slack通知を利用するプロジェクト側でも設定が必要になる.
https://github.com/higanworks/rundeck-slack-incoming-webhook-plugin#configuration
YOUR PROJECT > Project Settings > Edit Configuration > Edit Configuration File
に,以下のようにしてSlack側で作成したIncoming WebhooksのURLを設定する.
project.plugin.Notification.SlackNotification.webhook_url=https\://hooks.slack.com/services/hogehoge/fugafuga
ここまでやっておくと,プロジェクトのジョブ設定から,通知でSlack通知の設定が可能となる.
その他注意事項
Cluster Mode
RundeckにはCluster Modeというのが用意されている. が,Docker版であればデフォルトのままでCluster Modeとなっているので,特に気にすることはない.
ssh鍵
今回,コマンドを実行するにあたってssh鍵は用意していないので触れていないが,これもちゃんとDBに保存することができる.
Rundeckではssh鍵の保存場所をKey Storageと呼んでいる.そしてこれには,file
と db
を選択することができる.
https://rundeck.org/docs/administration/security/key-storage.html
で,そのままセットアップすれば現状だとdb
になるように設定されている.
ので特に気にする必要はない.
ただし,DBに保存する際には,BLOBのテキストとして保存されるため,このままだと生データのssh鍵を保存することになる. これが気になる場合は,Storage Converterを使って暗号化することをおすすめする.
完成形
最終的な構成はこんな感じ.
sshするのであれば同じVPCに配置してある方が楽なのだが,もうこの形式になってしまえばVPCは関係なくて,基本的にはすべてIAM Roleだけで権限管理が完結する.
そして用意したDockerfile
FROM rundeck/rundeck:3.0.8 USER root RUN set -ex && \ apt-get update && \ apt-get install -y unzip && \ rm -rf /var/lib/apt/lists/* && \ wget https://github.com/h3poteto/ecs-task/releases/download/v0.1.2/ecs-task_v0.1.2_linux_amd64.zip && \ unzip -d /usr/local/bin ecs-task_v0.1.2_linux_amd64.zip && \ # https://github.com/rundeck-plugins/rundeck-s3-log-plugin # S3 pluginを用意 mkdir libext && cd libext && \ wget https://github.com/rundeck-plugins/rundeck-s3-log-plugin/releases/download/v1.0.5/rundeck-s3-log-plugin-1.0.5.jar && \ # Slack pluginを用意 wget https://github.com/higanworks/rundeck-slack-incoming-webhook-plugin/releases/download/v0.9.dev/rundeck-slack-incoming-webhook-plugin-0.9.jar && \ chown -R rundeck:rundeck /home/rundeck/libext && \ mkdir /etc/rundeck && \ chown -R rundeck:rundeck /etc/rundeck ADD entrypoint.sh /home/rundeck/docker-lib/entrypoint.sh RUN chown rundeck:rundeck /home/rundeck/docker-lib/entrypoint.sh USER rundeck ENTRYPOINT [ "docker-lib/entrypoint.sh" ] CMD [ "docker-lib/entry.sh" ]
と,entrypoint.shによる環境変数設定.
#!/bin/bash export AWS_DEFAULT_REGION=ap-northeast-1 # DBの環境変数を埋めるだけでstorage.providerもDBにしてくれる export RUNDECK_DATABASE_URL=postgresql_url export RUNDECK_DATABASE_USERNAME=rundeck export RUNDECK_DATABASE_PASSWORD=hogehoge export RUNDECK_DATABASE_DRIVER=org.postgresql.Driver # https://なアドレスを指定すればSSL対応される export RUNDECK_GRAILS_URL=https://rundeck.example.com export RUNDECK_SERVER_ADDRESS=0.0.0.0 # https://rundeck.org/docs/administration/security/configuring-ssl.html#using-an-ssl-terminated-proxy # に従って # https://github.com/rundeck/rundeck/blob/v3.0.8/docker/official/lib/entry.sh#L34 # この設定を上書きしてELB側でSSLさせる export RUNDECK_SERVER_FORWARDED=true # execution logをS3に保存するための設定 export RUNDECK_PLUGIN_EXECUTIONFILESTORAGE_NAME=org.rundeck.amazon-s3 export RUNDECK_PLUGIN_EXECUTIONFILESTORAGE_S3_BUCKET=rundeck-executionlog export RUNDECK_PLUGIN_EXECUTIONFILESTORAGE_S3_REGION=ap-northeast-1 # githbuログインのためPreauthenticated Modeを有効化する # https://qiita.com/minamijoyo/items/52041ff8628263355810#preauthenticated-mode%E3%81%AE%E6%9C%89%E5%8A%B9%E5%8C%96 # https://github.com/rundeck/rundeck/blob/v3.0.8/docker/official/remco/templates/rundeck-config.properties#L22-L31 export RUNDECK_PREAUTH_ENABLED=true export RUNDECK_PREAUTH_USERNAME_HEADER=X-Forwarded-User export RUNDECK_PREAUTH_REDIRECT_LOGOUT=true exec "$@"
できたできた.