EKS on FargateでArgoWorkflowを利用したJob実行基盤の構築

通常、EKS上でジョブを実行する場合、Webサービス等の実行ノードと同じかもしくは分けてバッチジョブ用のノードを用意することになるかと思いますが、ノードを分けない場合は、バッチ処理による負荷がサービスへ影響を与える可能性がありますし、ノードを分ける場合は、利用しない間も起動しておく必要があることからコスト面が課題になってきます。
そのため、バッチ処理をFargate上で実行することで、処理の負荷の影響を切り離してかつ必要な時に必要なリソースだけ利用することでコストの最適化も行えるのではないかと考え検証を行いました。今回は検証過程で得た知見を紹介したいと思います。
バッチ処理でEKS on Fargateを使う理由
同一ノード上でバッチ処理を行うとどうしても一定時間、CPUやメモリを占有するということが起きてしまいます。そのためどうにかして負荷を下げようとすると、まずはバッチ専用ノードを立てるという案が出てきますが、これはバッチ処理が1日に数回程度の利用状況だとコスト的に見合わないことになります。
次に考えられる案として、AWS前提の話となりますが、LambdaやAWS Batchの利用かと思います。現状、弊社では開発プロセスをkubernetesに寄せた形にしているため、それに関する知見も多く蓄積されているので、ここであえて開発者にLambdaやAWS Batchを学習してもらう必要もないと考えました。
これを検討し始めた時点ではLambdaのコンテナイメージ利用やAWS BatchのFargate利用などの機能がまだ出ていなかったので上記のような結論になりましたが、こうした機能が使えるのであれば、社内にある知見を活用しつつうまく利用できる方法を検討していきたいと考えています。
こうした状況を踏まえて、kubernetesの知見を活かしつつバッチ処理の負荷を切り離す方法としてFargateの利用が最も効果的ではないかと考えました。
EKS on FargateでのJobの実行方法
Fargate上でPodを実行するための設定はそれほど難しくありません。Fargate Profileを設定し、そこで指定したNamespaceでJobをapplyするだけでFargateノードでJobが実行されます。
また、FargateProfileは既存のクラスタに対して後から追加可能となっており、Fargateノードで起動するかどうかはNamespaceだけでなくlabelで制御することも可能です。
以下はeksctlのcluster.yamlへの設定方法になります。対象のnamespaceがfargate-testとなり、labelにfargate: onが指定されたJobのみFargateノードで起動される設定になります。
: fargateProfiles: - name: fargate-profile selectors: - namespace: fargate-test labels: fargate: 'on'
FargateProfileの作成
eksctl create fargatargateprofile -f cluster.yaml
EKS on FargateでJobを実行する時の問題点
テスト用のJobをFargate上で動かしたところ、本番運用にあたっていくつかの問題に気づきました。
ジョブの実行完了後に誰がジョブを削除するか?
Jobの実行後にステータスがCompletedとなったPodは意図的に削除しない限り残り続けることとなります。EC2ノード上だと実行が完了しているので、それでリソースが使われるということはなく単にゴミが残るだけとなりますが、Fargateの場合、Fargateノードも残ることとなりその分課金され続けてしまいます。EKSではPodの終了後に自動で削除してくれるttlSecondsAfterFinishedを使うことはできないので、Podの削除方法については自前で仕組みを検討する必要があります。
モニタリング、ログの取得
EKSの監視はDatadogなどのagentをDaemonsetで起動させてモニタリングしてるケースが多いかと思いますが、Fargateではコンテナのみの提供となるためDaemonsetは使えません。そのため、sidecar方式でPodにagentを同居させる必要があります。しかしながら、こうしたagent系のコンテナをsidecarとして実行させると、メインのJobの実行は完了したもののagentが終了しないのでJobがいつまでも起動したままとなります。そのため、どうにかしてagentをJobの完了に合わせて終了させる必要があります。
以下のブログはアプリの終了時にsidecarをkillするということをやっているようです。
tech.recruit-mp.co.jp
そしてこのブログを書いている間に、reinvent2020が始まり以下の機能がリリースされました。ログに関してはsidecarで取得することなくcloudwatch logsに送ることが可能になりました。
aws.amazon.com
起動時間の考慮
Fargateノードで起動する場合、ノードを起動するまで思ったよりも時間がかかりました。検証時には毎回2-3分かかりました。調べたところ以下のブログで検証されているのですが、起動時間の大半はイメージのpullにかかっているため、主にイメージサイズに依存します。そのため、時間どおり正確に処理を実行したいなどの要件がある場合は、実行基盤としてはそぐわない場合もあると思います。
qiita.com
DaemonsetでデプロイされたPodが起動しようとする
検証時のクラスターにはEFS CNIドライバーが起動されていました。テスト用のJobを実行すると、Fargateノードの起動を検知して同様にEFS CNDドライバもFargateノードで起動しようとする動きが見られました。EFS CNIドライバーはFargateノードではエラーで起動できなかったのでメインのPodの削除とともに消えましたが、ものによっては起動が成功するかもしれないので、環境によっては注意が必要になります。
開発者とインフラ担当者の責任分界点
弊社では、モニタリングやログ取得については、インフラ担当の責任範囲としており、アプリケーションに関するManifestは開発者の責任範囲としています。しかし、Fargateを利用するとモニタリングやログの取得はsidecar方式での利用となるため、設定を開発者へ委ねることになります。また、Fargateノードで起動するしないの設定も開発者に意識してもらう必要がありますので、こうした点を意識して設定してもらう必要があります。
その他
他にも、現状では、SecurityGroupが任意のものを設定できないといった問題がありました。このあたりは、今後の改善に期待ですね。
Argo Workflowの利用
EKS on FargateでJobを実行する場合の大きな問題点は、上記で書いたようにJob実行後のPodをどう削除するかとsidecarとなるコンテナをどのようにkillするかという点になるかと思います。これらを解決できないとJob実行後もFargateノードが残り続けることとなるためFargateノードを使う意味がなくなります。何か良い方法がないか調べていたところ、Argo Workflowを利用することで解決できそうなことがわかりました。
Argo WorkflowとはオープンソースのJobのワークフローエンジンとなり、UIでジョブ管理ができるツールになります。詳細は以下のリンクを参照してください。Argo WorkflowでJobを管理、実行することで完了後のPodも自動で削除してくれますし、メインのコンテナ終了後にsidecarコンテナもあわせてkillしてくれるので、Jobを実行するにあたっての大きな懸念点が解消されました。
argoproj.github.io
インストール及び設定
インストール方法
インストール方法については、cluster-installとnamespace-installの大きく2種類があります。
cluster-installとはクラスタのどのnamespaceでもJobを実行できるようにするインストール方式です。一方でnamespace-installは、argo workflowをインストールしたnamespaceと同じnamespaceでしかJobを実行できません。
現状ではargoworkflowはユーザーごとの権限制御があまり細かく設定できなさそうなので、namespaceごとにユーザーを限定したいのであればnamespace-installを利用すればいいかと思います。今回はnamespaceごとにインストールしたくはないのとあまり細かい制御は必要ないのでcluster-installとしました。
gitからargo workflowの取得
git clone https://github.com/argoproj
configmapを編集
Fargate上で動かすためには、ランタイムの設定であるcontainerRuntimeExecutorをk8sapiに変更する必要があります。ここが少しハマったポイントなのですが、デフォルト設定のdockerのままだと実行したJobのステータスがPendingのまま実行されませんでした。
また、workflowのデフォルト設定もできるので、デフォルトの値を設定しておくことで実行時のミスを減らせるかと思います。他の項目については必要に応じて編集してください。
cd argo/manifests/ vim base/workflow-controller/workflow-controller-configmap.yaml
workflow-controller-configmap.yaml
apiVersion: v1 kind: ConfigMap metadata: name: workflow-controller-configmap data: config: | : : containerRuntimeExecutor: k8sapi : :
kustomizeでビルドし、applyしてください。
cd cluster-install kustomize build . > install.yaml kubectl apply -f install.yaml -n argo
ポートフォワードします。必要に応じてALBなどを設定してください。
kubectl -n argo port-forward deployment/argo-server 2746:2746
以下のURLにアクセスするとArgo WorkflowのUIにアクセスできます。本番用途ではALBの利用やSSOなどの利用を検討してもいいかと思います。
http://localhost:2746
サービスアカウント
Argo WorkflowでJobを実行するには、以下の権限を持ったサービスアカウントが必要になります。以下の内容を保存してapplyしてください。
AWSのリソースにアクセスする必要がある場合は、eksctlなどでサービスアカウントを作成しているかと思いますが、そこで作成したサービスアカウントに以下のロールをバインドする必要があります。このあたりはeksctlで一括管理したいですが、今のところいい方法はなさそうです。
vim fargate-serviceaccount.yaml
apiVersion: v1 kind: ServiceAccount metadata: name: fargate-serviceaccount namespace: fargate-test --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: workflow-role rules: - apiGroups: - "" resources: - pods verbs: - get - watch - patch - apiGroups: - "" resources: - pods/log verbs: - get - watch --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: argo-batch-binding namespace: fargate-test subjects: - kind: ServiceAccount name: fargate-serviceaccount namespace: fargate-test roleRef: kind: Role name: workflow-role apiGroup: rbac.authorization.k8s.io
fluentbitの設定
Jobを実行するnamespaceでfluentbit用のConfigMapを設定しておきます。
以下はログをdatadogに送る場合のサンプルになります。DatadogのAPIキーが必要になります。
apiVersion: v1 kind: ConfigMap metadata: name: fluentbit-config namespace: fargate-test data: # Configuration files: server, input, filters and output # ====================================================== fluent-bit.conf: | [INPUT] Name tail Tag *.logs Path /var/log/*.log DB /var/log/logs.db Mem_Buf_Limit 5MB Skip_Long_Lines On Refresh_Interval 10 [OUTPUT] Name datadog Match * compress gzip apikey [your datadog api key] dd_service hello-world dd_source job dd_tags vendor:sample,env:stg
Jobの実行
以下はJobのサンプルになります。5秒ごとに「hello world」を出力するだけのサンプルです。
apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: hello-world- spec: entrypoint: helloworld templates: - name: helloworld serviceAccountName: fargate-serviceaccount metadata: labels: fargate: "on" container: image: busybox args: - /bin/sh - -c - > for i in 1 2 3 4 5; do echo hello world >> /var/log/app.log; sleep 5; done volumeMounts: - name: varlog mountPath: /var/log resources: requests: memory: 512Mi cpu: 500m sidecars: - name: fluentbit-agent image: amazon/aws-for-fluent-bit:latest ports: - containerPort: 2020 env: - name: FLUENTD_HOST value: "fluentd" - name: FLUENTD_PORT value: "24224" - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace volumeMounts: - name: varlog mountPath: /var/log - name: fluentbit-config mountPath: /fluent-bit/etc/ volumes: - name: varlog emptyDir: {} - name: fluentbit-config configMap: name: fluentbit-config ttlStrategy: secondsAfterCompletion: 30 secondsAfterSuccess: 30 secondsAfterFailure: 30
以下設定ポイントになります。
- namesapceとlabelsの[fargate: 'on']設定がFargateで起動するかどうかを制御します。
- アプリケーションからログを/var/log/app.logにリダイレクトすることでfluentbitがログを収集します。
- resourcesでのリソース要求設定で必要なFargateのスペックが割り当てられます。
- ttlStrategyでJobが完了した場合などで、何秒後にPodを削除するか設定します。
fargateラベルやttlStrategyなどは設定が漏れると困るのでデフォルト値として設定しておいてもいいかもしれません。
上記のマニフェストファイルを適当な名前で保存してUIから実行するか以下のコマンドを実行するとジョブが実行されます。
argo submit helloworld.yaml
ArgoWorkflowによるsidecarのkill制御
ArgoWorkflowでは、mainコンテナが終了するとsidecarコンテナもkillしてJobを終了させてくれますが、sidecarコンテナが複数あるとtimeoutが発生し、一つしかkillされませんでした。コントローラ用のコンテナからはシグナルを全てのコンテナに送っているものの片方しか終了させれない状況となっていました。そのため、当初はfluent-bitとdatadog-agentをsidecarとして配置し、ログとメトリクスを取得する予定でしたが、メトリクスの取得は断念しました。
しかし、上記でも紹介しましたがEKS on Fargateで直接Fargateのログをcloudwatch logsなどに送ってくれる機能追加が行われたため、これを利用しつつdatadog-agentをsidecarとして配置することでメトリクスを取得できそうです。ただし、現時点では利用中の環境のEKSプラットフォームバージョンが上がってないためこの機能を利用できないので、プラットフォームバージョンが上がるまで待つこととしました。ちなみにEKSのプラットフォームバージョンは利用者が意図的にあげることはできないようです。AWS側で定期的にプラットフォームのバージョンを更新しているためそれを待つ必要があります。どうしてもアップさせたい場合は、新しくクラスタを作り直すかkubernetesのメジャーバージョンをあげる必要があります。
まとめ
EKS on Fargateでのバッチ処理については、ある程度のデータ量を集計する必要があるなど、まとまったリソースが必要な処理には適していると思います。
向いていない処理としては、起動時間に数分必要となるので、正確な時間に実行する必要のある処理や毎分実行する処理、またイベント駆動的な処理などにもあまり適していないと感じました。
また、ログはEKSの標準機能でCloudWatchlogsから取得できるようになりましたが、メトリクスを取得するにはsidecarで取得せざるを得ない状況です。sidecarをArgoWorkflowで制御できるとはいえ、Jobごとに書く必要があるため冗長になってしまいます。できればメトリクスについてもcloudwatchから取得できるようにして欲しいところです。AWSコンソールでもkubernetesのワークロードが見れるようになったので、近いうちに対応してくれるのではないかと期待しています。
バッチ処理のようなバックグラウンドで行われる処理の負荷をどう下げるかというのは頭を悩ませていましたが、Fargateをうまく活用することで一時的な負荷を切り離しつつかつコストも抑えれることが分かりましたので、今後は色々な環境で利用していきたいと思います。
GuacamoleがSAML対応したのでOneloginと連携してみた

前提
以前のブログで構築したGucamole環境(Docker)をベースに設定を進めますので、基本の環境についてはこのブログを参照してください。
構成
ELB + EC2構成とします。
設定手順
SAMLモジュールの取り込み
公式で提供されているguacamole(1.2.0)のDockerイメージについては、ldapモジュールのように起動時に自動で取り込みしてくれるような設定となっていません。そのため、SAMLモジュールを取り込んだイメージを作成する必要があります。このイメージについては、こちらにも置いてありますので、直接参照してもらってもいいのですが、ここではローカルでビルドする想定で手順を記載しています。
Oneloginの設定
「Applications」タブを選択し、「Add App」をクリックして、「SAML Test Connector」を検索してください。一覧から「SAML Test Connector (IdP w/ attr w/ sign response)」を選択します。
Display Nameに適当な名前を入力し保存します。
保存後に右上の「More Actions」からSAML Metadataをダウンロードします。
左ペインの「Configuration」を選択し、「Recipient」、「ACS (Consumer) URL Validator」、「ACS (Consumer) URL」に以下のURLを設定し、保存します。
http://[YOUR_ELB_DNS]/guacamole/api/ext/saml/callback
Usersでログイン可能なユーザーを追加しておいてください。
docker-composeの作成
以下の通りdocker-composeファイルを作成してください。guacamoleのイメージについては、Docker Hubから取得するかlocalから取得するかによって内容を変更してください。
SAML_IDP_URLは利用しているのIDPのURLになります。oneloginの場合、以下のようなURLとなります。
https://[YOUR_SUBDOMAIN].onelogin.com/
SAML_ENTITY_IDについては、oneloginから取得したmetadataファイルにENTITY_IDがありますので、それを記載します。
SAML_CALLBACK_URLについては、guacamoleのURLを記載してください。なぜこの設定が必要なのかはドキュメントを読んでもよく分かりませんでした。また、設定項目としてSAML_IDP_METADATA_URLという項目があるので、これだけ設定すればいけるかと思いましたが、うまく動作しなかったので最終的に上記の設定項目としました。
version: "3" services: guacd: image: guacamole/guacd:1.2.0 restart: always expose: - "4822" environment: - GUACD_LOG_LEVEL=debug guacamole: # From Docker Hub #image: cloudfish/guacamole-client-saml:1.2.0 # From local image: guacamole-client-saml:1.2.0 restart: always links: - guacd:guacd ports: - "8080:8080" environment: - GUACD_HOSTNAME=guacd - SAML_IDP_URL=[YOUR_IDP_URL] - SAML_ENTITY_ID=[YOUR_ENTITY_ID] - SAML_CALLBACK_URL=http://ELB_DNS/guacamole - MYSQL_HOSTNAME=mysql - MYSQL_DATABASE=guacamole - MYSQL_USER=guacamole - MYSQL_PASSWORD=guacamole mysql: image: mysql/mysql-server:5.7 restart: always volumes: - "./mysql/data:/var/lib/mysql" - "./mysql/init:/docker-entrypoint-initdb.d" expose: - "3306" ports: - "3306:3306" environment: - MYSQL_DATABASE=guacamole - MYSQL_USER=guacamole - MYSQL_PASSWORD=guacamole||< ** ディレクトリの作成 docker-compose.yamlファイルと同じ階層に以下のディレクトリを作成します。 >|csh| mkdir -p mysql/data mkdir -p mysql/init
tree -d mysql/ mysql/ ├── data └── init
初期データ作成
以下のコマンドで初期化用のスクリプトを作成します。
docker run --rm guacamole-client-saml:1.2.0 /opt/guacamole/bin/initdb.sh --mysql > mysql/init/initdb.sql
ログイン確認
以下のURLにアクセスするとOneloginのログイン画面にリダイレクトされます。
http://ELB_DNS/guacamole/
ポータル画面から「Guacamole」選択します。
正しく設定できていれば、Guacamoleにログインできます。
SAML設定をすると管理者ユーザー(guacadmin)でログインできなくなります。そのため、予めSAML設定を外して起動することでパスワードログインが可能になりますので、Oneloginユーザーと同名のユーザーを作成して管理者権限を付与しておく必要があります。
OneloginでRedashのSAML設定

Redashの構築
まずはRedashを構築しましょう。Redash公式でAMIを用意してくれていますので今回はそれを使います。
構成図
構成については、通常のELB + EC2(Redash)で構築します。
Redash
ブログ執筆時点の東京リージョンでのAMIはami-060741a96307668beになりますので、これを起動します。
各リージョンで提供されているAMIは以下を参照してください。正常に起動できればELBにアタッチしておいてください。
redash.io
NATが用意されているのであれば、SSMも使えるのでプライベートサブネットで起動する方がセキュアだと思います。
Onelogin側でSAML設定
「Application」から「Add App」をクリックし、「SAML Test Connector (IdP w/ attr w/ sign response)」を選択します。
Display Nameを入力しSaveします。
Configuration
Recipient、ACS (Consumer) URL Validator、ACS (Consumer) URLに以下を設定します。
http://ELB_DNS/saml/callback?org_slug=default
Parameter
カスタムパラメータとしてFirstNameとLastNameを追加します
FirstName
LastName
Metadataの取得
「More Actions」の「SAML Metadata」を右クリックでリンク先のURLをコピーして控えたうえでMetadataをダウンロードする。
ダウンロードしたMetadataを開いてentityIDとNameIDFormatの値を控えておきます。
RedashのSAML設定
管理者でSettingから以下の設定を行う。
SAML Enabled | チェック |
---|---|
SAML Metadata URL | metadataのURLをセット |
SAML Entity ID | metadata内のentityIDをセット |
SAML NameID Format | metadata内のNameIDFormatをセット |
ログイン確認
設定完了後にSAMLでログインすると、以下のようなエラーが発生します。
コンテナのログを確認すると以下のようなエラーが発生していました。
server_1 | [2020-06-22 03:08:51,313][PID:9][INFO][saml2.entity] HTTP REDIRECT server_1 | [2020-06-22 03:08:51,314][PID:9][INFO][metrics] method=GET path=/saml/login endpoint=saml_auth_sp_initiated status=302 content_type=text/html; charset=utf-8 content_length=1723 duration=441.55 query_count=1 query_duration=1.04 nginx_1 | 139.101.233.32 - - [22/Jun/2020:03:08:51 +0000] "GET /saml/login?next=%2Fsettings%2Forganization HTTP/1.1" 302 1723 "http://ec2-54-249-199-98.ap-northeast-1.compute.amazonaws.com/login?next=http%3A%2F%2Fec2-54-249-199-98.ap-northeast-1.compute.amazonaws.com%2Fsettings%2Forganization" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" "-" server_1 | [2020-06-22 03:08:53,456][PID:10][INFO][saml2.response] status: <?xml version='1.0' encoding='UTF-8'?> server_1 | <ns0:Status xmlns:ns0="urn:oasis:names:tc:SAML:2.0:protocol"><ns0:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></ns0:Status> server_1 | [2020-06-22 03:08:53,458][PID:10][ERROR][saml2.client_base] XML parse error: Signature missing for assertion server_1 | [2020-06-22 03:08:53,459][PID:10][ERROR][saml_auth] Failed to parse SAML response server_1 | Traceback (most recent call last): server_1 | File "/app/redash/authentication/saml_auth.py", line 73, in idp_initiated server_1 | entity.BINDING_HTTP_POST) server_1 | File "/usr/local/lib/python2.7/site-packages/saml2/client_base.py", line 702, in parse_authn_request_response server_1 | binding, **kwargs) server_1 | File "/usr/local/lib/python2.7/site-packages/saml2/entity.py", line 1170, in _parse_response server_1 | response = response.verify(keys) server_1 | File "/usr/local/lib/python2.7/site-packages/saml2/response.py", line 1018, in verify server_1 | if self.parse_assertion(keys): server_1 | File "/usr/local/lib/python2.7/site-packages/saml2/response.py", line 930, in parse_assertion server_1 | if not self._assertion(assertion, False): server_1 | File "/usr/local/lib/python2.7/site-packages/saml2/response.py", line 781, in _assertion server_1 | raise SignatureError("Signature missing for assertion") server_1 | SignatureError: Signature missing for assertion server_1 | [2020-06-22 03:08:53,465][PID:10][INFO][metrics] method=POST path=/saml/callback endpoint=saml_auth_idp_initiated status=302 content_type=text/html; charset=utf-8 content_length=219 duration=560.91 query_count=1 query_duration=1.02
エラーでググってみたところ以下のような対処方法が見つかりましたのでソースを修正します。
stackoverflow.com
Redashの修正
上記のエラーを回避するためソースを修正する必要があります。そのため、Redashコンテナのイメージを作り直します。
ソースの取得
まずは、Redash用サーバーにsshログインします。公式AMIはv8.0.0がベースになっていますので、v8系のソースを取得します。以下のコマンドでv8.0.2をが取得できます。
git clone https://github.com/getredash/redash.git git checkout a16f551e22c6288df6f067aa12caa5afd9a8f1dd
saml_auth.pyの修正
以下のソースを修正します。
/redash/redash/authentication/saml_auth.py
以下のオプションをTrueからFalseに修正してください。
'want_assertions_signed': False,
コンテナのビルド
インスタンスタイプがt2.smallだとビルドが途中で失敗しました。t2.mediumかt2.largeくらいあった方がいいかもしれません。
docker build -t redash_onelogin .
docker-composeの修正
以下のファイルのdockerイメージを修正します。
/opt/redash/docker-compose.yml
version: "2" x-redash-service: &redash-service #image: redash/redash:8.0.0.b32245 image: redash_onelogin depends_on: - postgres - redis env_file: /opt/redash/env restart: always services: server: <<: *redash-service command: server ports: - "5000:5000" environment: REDASH_WEB_WORKERS: 4 scheduler: <<: *redash-service command: scheduler environment: QUEUES: "celery" WORKERS_COUNT: 1 scheduled_worker: <<: *redash-service command: worker environment: QUEUES: "scheduled_queries,schemas" WORKERS_COUNT: 1 adhoc_worker: <<: *redash-service command: worker environment: QUEUES: "queries" WORKERS_COUNT: 2 redis: image: redis:5.0-alpine restart: always postgres: image: postgres:9.6-alpine env_file: /opt/redash/env volumes: - /opt/redash/postgres-data:/var/lib/postgresql/data restart: always nginx: image: redash/nginx:latest ports: - "80:80" depends_on: - server links: - server:redash restart: always ||< 以下のコマンドで一旦ストップさせてコンテナを再起動させます。 >|csh| docker-compose stop docker-compose up -d
ログイン確認
再度ログイン確認したところ無事ログインできました。
まとめ
RedashのSAML連携は簡単にできそうでしたが、意外とハマりどころも多く、またOneloginの設定方法もわかっていなかったので思ったより時間がかかりました。もし同じような組み合わせで検討されているようであれば参考にしていただけたらと思います。
OneloginとGuacamoleでSSO設定

Gucamoleについて詳しく知りたい方は、以前書いたブログを参照してみてください。Guacamoleの現在のバージョン(v1.1.0)では、残念ながらSAML連携はできません。(実装は進められているようです。)そのため、OneloginのVirtual LDAPを使ってSSO連携させたいと思います。
前提
以前のブログで構築したGucamole環境をベースに設定を進めます。
OneloginでVLDAPを設定する
以下のようにVLDAPサービスを有効化します。
stunnelコンテナの構築
OneloginのVLDAPにはLDAPS(SSLまたはTLSを使用してLDAP通信する)での接続となります。Guacamoleから直接LDAPSで接続するのは少し大変そうなのでstunnelにプロキシさせることとしました。そのためGuacamoleからはstunnelにLDAP接続します。
まずはstunnelコンテナを作ります。前回立てたサーバーにssh接続を行い、適当なディレクトリで以下の2つのファイルを作成します。
Dockerfileの作成
FROM ubuntu
RUN apt-get -y update && apt-get -y upgrade
RUN apt-get -y install stunnel4
COPY docker-entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
CMD ["/entrypoint.sh"]
docker-entrypoint.shの作成
#! /bin/sh # Create configulation cat <<EOF > /etc/stunnel/ldap.conf foreground = yes [ldap] client = yes accept = $ACCEPT_IP_ADDR:$ACCEPT_PORT connect = $LDAP_CONNECT_URL:$LDAP_CONNECT_PORT EOF # enable stunnel echo ENABLED=1 >> /etc/default/stunnel4 # start stunnel4 echo "Start stunnel4" /usr/bin/stunnel4 /etc/stunnel/ldap.conf
イメージのビルド
以下のコマンドでイメージをビルドします。正常に作成できていればstunnel_oneloginという名前でイメージが作成されるはずです。
docker build -t stunnel_onelogin .
docker-composeの作成
${your_domain}には自身のOneloginのサブドメインを設定してください。
stunnelコンテナは、ポート1636でguacamoleコンテナから接続を待ち受けを行い、接続があればOneloginのVLDAPのエンドポイント(ldap.us.onelogin.com)のポート636に接続します。
version: "3" services: guacd: image: guacamole/guacd:latest restart: always expose: - "4822" environment: - GUACD_LOG_LEVEL=debug guacamole: image: guacamole_latest restart: always links: - guacd:guacd ports: - "8080:8080" environment: - GUACD_HOSTNAME=guacd - GUACD_LOG_LEVEL=debug - LDAP_HOSTNAME=stunnel - LDAP_PORT=1636 - LDAP_ENCRYPTION_METHOD=none - LDAP_USER_BASE_DN=cn=email,ou=users,dc=${your_domain},dc=onelogin,dc=com - LDAP_USERNAME_ATTRIBUTE=cn - LDAP_CONFIG_BASE_DN=ou=users,dc=${your_domain},dc=onelogin,dc=com #- TOTP_ENABLED=true - MYSQL_HOSTNAME=mysql - MYSQL_DATABASE=guacamole - MYSQL_USER=guacamole - MYSQL_PASSWORD=guacamole mysql: image: mysql/mysql-server:5.7 restart: always volumes: - "./mysql/data:/var/lib/mysql" - "./mysql/init:/docker-entrypoint-initdb.d" expose: - "3306" ports: - "3306:3306" environment: - MYSQL_DATABASE=guacamole - MYSQL_USER=guacamole - MYSQL_PASSWORD=guacamole stunnel: image: stunnel_onelogin restart: always ports: - "1636:1636" environment: - ACCEPT_IP_ADDR=0.0.0.0 - ACCEPT_PORT=1636 - LDAP_CONNECT_PORT=636 - LDAP_CONNECT_URL=ldap.us.onelogin.com
起動
以下のコマンドで起動します。
docker-compose up -d
ログイン確認
ユーザー名はOneloginで登録しているメールアドレスになります。パスワードもOneloginで登録したものになります。
ログインできない場合は、ログの内容を確認して対処してください。
まとめ
Guacamoleは現状SAML連携ができないため、SSOを行うにはLDAPを使うしかありませんが、IDPによってはLDAP接続を提供しているものもあるため検討の余地はあると思います。
また、MFAについては、Onelogin側で設定も可能となっています。Guacamole側でMFA設定も可能ですが、端末を紛失した際の初期化対応が少し手間なので、MFA設定が必要であればOneloginで設定するのもありだと思います。また、VLDAPのエンドポイントに対して接続元のIP制限もかけれるため、よりセキュアにすることも可能です。
Gucamoleのユーザー認証の方法の一つとしてこういったIDPを検討してもいいのではないでしょうか。
Kubeviousでkubernetesの状態を確認する
Kubevious is 何?
kubeviousとはkubernetestの設定をビジュアルに確認できるオープンソースソフトウェアです。これだけだと他にも似たようなツールはあるのですが、いくつか面白い機能があったので触ってみました。
実際に動作するデモが用意されていますのでインストールする必要もありません。なにわともあれ触ってみましょう。
基本的には状態を参照するだけのツールとなっており、今のところ設定の変更はできないようです。
demo.kubevious.io
Namespaceごとに構成しているリソースががまとめられて表示されています。見たいNamespaceを選択するとDeployment、Service、ServiceAccount、Config等々の各リソースの詳細を確認することが可能です。
例えばDeploymentを選択するとManifestの内容が確認できます。
主な機能
Detects Configuration Errors
設定エラーや使われていないリソースがあったりするとAler欄にその内容を表示してくれます。
Enables Full Text Search
設定内容から全文検索が可能です。例えば8080で検索すると以下のようにポート8080を利用している箇所がヒットします。Grepよりインタラクティブに結果が確認できるのでサクッと設定内容を検索したい場合は便利だと思います。
Radioactive & Overprivileged Workloads
特権コンテナや、ホストのネットワークを利用しているリソースなどを放射能マークで知らせてくれます。
Kubeviousのデプロイ
インストールは特にハマることはなく以下のコマンドデプロイできました。
kubectl create namespace kubevious helm repo add kubevious https://helm.kubevious.io helm upgrade --atomic -i kubevious kubevious/kubevious --version 0.5.9 -n kubevious
ポートフォワードでアクセスします。
kubectl port-forward $(kubectl get pod -l k8s-app=kubevious-ui -n kubevious -o jsonpath="{.items[0].metadata.name}") 3000:3000 -n kubevious
以下のURLにアクセスするとKubeviousが表示されます。
http://localhost:3000
まとめ
Production環境で使うほどのツールではないかもしれませんが、Staging環境などで設定漏れや使ってないリソースの確認など俯瞰的に状態を確認したりするのにはすごく便利なツールではないでしょうか。できればクラスタを一括管理できればもっと便利に使えるかもしれませんね。みなさん興味があれば是非使ってみてください。
参考
Building Apache Guacamole with AWS Fargate and Aurora Serverless

I can build Gucamole with docker-compose, but I considered scale out If there are more increase RDP connections.So, I have choosen building with AWS Fargate because it's easy to scale out.
In the first place ,Can the Guacamole scale out ?I didn't find it at the official documentation.I found that somebody ask it in mailing list, the answer was yes because of using proper transactions. I believe this answer, I decided to build with AWS Fargate for scaling.
AWS Fargate is a serverless compute engine for containers without need to manage Fargate Hosts.
Unlike EKS, we don't need to pay for control plane. you only pay amount of using fargate container.We can focus building application.
And the reason of choosing Aurora Serverless is cheap of the cost, because it will stop if you don't access by constantly. Also it's scaled out if neccesarry. Currently, the number of users is low, don't need high performance. threfore, the first access is acceptable even if it is late.
So let's get started!
Overview
The point of this configuration.
- Host guacd and gucamole on Fargate
- Host mysql on Aurora Serverless
- Manage user info on SimpleAD
Note that the recording function is not used this time.
Diagram
1. SImpleAD
Create SimpleAD as follows
DirectoryType | small |
---|---|
Domain | any name |
You can add users from the Workspaces service without launching Workspaces.
2.Aurora Serverless
2.1 Create Database instance
Create it with the following settings. Here are the main settings
Engine options | Amazon Aurora |
---|---|
Edition | Amazon Aurora with MySQL compatibility |
Version | latest |
Database Location | Regional |
Database features | Serverless |
DB cluster identifie | any name |
Minimum Aurora capacity unitInfo | 1 |
Maximum Aurora capacity unitInfo | 1 |
Additional scaling configuration | check 'Pause compute capacity after consecutive minutes of inactivity' |
Web Service Data API | check 'Data API' |
When you're done creating,you memo RDS Endpoint.
3. Application Load Balancer
3.1 Create Target Group
Create target group as follows
Target Type | IP |
---|---|
Protocol | HTTP |
Port | 8080 |
HealthCheck
Protocol | HTTP |
---|---|
Path | /guacamole |
Stickiness | Enable |
Stickiness duration | 3600 |
4. Create Fargate Cluster
4.1 Create Security Group
Create new SecurityGroup as follows for fargate task.
Protocol | Port | Source |
---|---|---|
TCP | 8080 | SecurityGroup of ALB |
When it complete, you modify SecurityGroup of RDS.
Add the following setting.
Protocol | Port | Source |
---|---|---|
TCP | 3306 | SecurityGroup of Fargate task |
4.2 Create Fargate Cluster.
Execute the following command for creating cluster.
ecs-cli configure \ --region ${YOUR_REGION_CODE} \ --cluster ${CLUSTER_NAME} \ --default-launch-type FARGATE \ ecs-cli up \ --cluster-config $CLUSTER_NAME \ --vpc ${VPI_ID} \ --subnets ${SUBNET_ID_1},${SUBNET_ID_2}
4.3 Run the Guacamole Service
ecs-params.yml
create ecs-params.yml file as follows.
Set the variables according to your environment.
version: 1 task_definition: task_role_arn: ${TASK_ROLE_ARN} task_execution_role: ${TASK_EXECUTION_ROLE} ecs_network_mode: awsvpc task_size: mem_limit: 0.5GB cpu_limit: 256 services: guacamole: secrets: - value_from: /guacamole/mysql_password name: MYSQL_PASSWORD run_params: network_configuration: awsvpc_configuration: subnets: - ${SUBNET_ID_1} - ${SUBNET_ID_2} security_groups: - ${SECURITY_GROUP_ID_FOR_TASK}
docker-compose.yml
create docker-compose.yml file as follows.
Also set the variables according to your environment.
version: "3" services: guacd: image: guacamole/guacd:latest expose: - "4822" ports: - "4822:4822" logging: driver: awslogs options: awslogs-group: guacamole awslogs-region: ap-northeast-1 awslogs-stream-prefix: guacd environment: - GUACD_LOG_LEVEL=debug guacamole: image: cloudfish/guacamole:latest ports: - "8080:8080" logging: driver: awslogs options: awslogs-group: guacamole awslogs-region: ap-northeast-1 awslogs-stream-prefix: guacamole environment: - GUACD_HOSTNAME=localhost - LDAP_HOSTNAME=${SIMPLE_AD_IP} - LDAP_PORT=389 - LDAP_ENCRYPTION_METHOD=none - LDAP_USER_BASE_DN=CN=Users,DC=guacamoleDC=local - LDAP_USERNAME_ATTRIBUTE=CN - LDAP_CONFIG_BASE_DN=CN=Users,DC=guacamole,DC=local - TOTP_ENABLED=false # If you want to use MFA, set true - MYSQL_HOSTNAME=${RDS_ENDPOINT} - MYSQL_DATABASE=guacamole - MYSQL_USER=guacamole
Launching the Service
Execute the following command and start Gucamole service.
ecs-cli compose \ --file docker-compose.yml \ --ecs-params ecs-params.yml \ --project-name guacamole-service \ service up \ --force-deployment \ --target-group-arn ${TARGET_GROUP_ARN} \ --container-name guacamole \ --container-port 8080
When If it's running properly, you can see the screen as follows on ECS Task screen. If it's not running, you check cloudwatch logs.
Login
Now, Let's get access to login screen.Access the following URL.If Aurora Serverless is stopped,It's going to take some time.
https://YOUR_DOMAIN/guacamole
You can see the this screen.
You can logged in by the following ID/PASS.
USER:guacadmin
PASSWORD:guacadmin
Register target device you want to connect, you try to connect with RDP. But I won't go into how to connect it in detail.
In this configuration, the recording function is not available, because this architecture don't have storage.But Fargate can be available EFS in platform 1.4. therefore, I'm sure you can use the EFS to record.
Apache Guacamoleを使ってWindowsServerにセキュアにログイン

しかしながら、昨今の急激なリモートワークへの移行に伴って、自宅からの接続も許可する必要が出てきましたが、現状オフィスへのVPN接続環境はないため、このままだと対象のWindowsマシンのセキュリティグループ設定でRDP接続の接続元をフル解放するか都度作業者のIPを許可する必要に迫られました。とはいえどちらの方法もあまり現実的ではないので、別な手段を探していたところ、GuacamoleというブラウザからRDP接続ができるツールを見つけたので、これをRDP接続するための踏み台サーバとして利用することで共通ユーザー問題を解決できると考え導入を進めています。今回はこれについて紹介したいと思います。
Apache Guacamoleとは
Guacamole(ワカモレもしくはガカモレと読むそうです)とはクライアントレスなリモートデスクトップゲートウェイです。また、接続クライアントに特別なプラグインやソフトウェアなどをインストールせずに、ブラウザからVNC、RDP、SSH接続などが可能となっています。
Guacamoleの主な機能
主に以下のような機能が提供されており、作業端末やサーバーへのアクセスにおいて、セキュリティの強化や問題が発生した際のトレースが行いやすくなっています。また、今回Guacamoleを使う大きな理由の一つとして、管理者側で接続可能な端末を登録して誰に接続させるかをコントロールできる機能があります。そのため利用者側は接続情報(ユーザー、パスワード)を意識する必要がありません。言い換えると利用者側はユーザー、パスワードを知ることができないことになります。
Gucamoleの構成
詳細は公式ドキュメントを確認してもらうとして、主に二つのコンポーネントで構成されています。
guacamole
ユーザーからのアクセスを受け付けるWebアプリケーション
guacd
RDP、VNC、SSH接続などを行うコンポーネント
guacamole.apache.org
現状の課題と解決策
本来であればWindowsをAD運用することが一番いいのですが、現状すぐにはその対応はできないというのが前提にあります。
1.ユーザー管理
現状では、ユーザー管理ができていないため、SimpleADを使ってGucamoleのユーザー管理を行います。もい担当者が異動や退職となって業務を外れた場合は、対象ユーザーを削除します。削除されたユーザーはGucamoleにログインできないため、業務用のWindowsServerへもログインでき無くなります。
2.WindowsServerへ共通ユーザーでログイン
WindowsServerへの接続情報はGucamoleだけが保持する事になるので、利用者が知ることはできません。そのため、担当者の退職などによりパスワードの再設定、再配布などの対応が不要になります。
3.いつ誰がアクセスしたか分からない
Gucamole側でいつ誰がどの端末にアクセスしたかログを保持しているため、Windows側は共通ユーザーでログインされていたとしてもGucamole側のログを確認することでトレースが可能となります。
構成
実際の構成はVPCが別れていたりするのでもう少し複雑にはなるのですが、ここではシンプルな構成とします。以下のようにGucamoleサーバーを踏み台として配置することでWindowsServerをPrivateSubnetに配置することが可能になります。
構築手順
Gucamoleのインストール方法については、サーバーにwarファイルを展開して直接インストールする方法もありますが、Dockerでインストールする方が簡単なので今回はこちらの方法で構築を行います。また、有償にはなりますがMarketPlaceでイメージ(Guacamole Bastion Host)が公開されているものもありますので興味のある方はそちらもご覧ください。
SimpleADの構築
ユーザー管理用のSimpleADを構築します。SimpleADの構築方法については以下のブログを参照してください。
以下の設定で構築してください。ドメイン名は適当な名称を設定してください。
DirectoryType | small |
---|---|
ドメイン | guacamole.local |
VPC | Gucamoleを構築するVPC |
Subnet | 上記のVPCのPrivateSubnet |
ユーザーの作成については、WorkSpacesの設定から追加が可能なのでGucamoleのログイン用ユーザーを作成しておいてください。こちらも以下のブログに追加方法がありますので参照してください。間違ってWorkspacesの立ち上げまではしないようにしてください。
Simple ADを利用したWorkSpaces構築手順 – サーバーワークスエンジニアブログ
ELBの準備
ELBはApplicationLoadBalancerを利用します。こちらも詳細は他のブログを参照してください。また、カスタムドメインを使用する場合は、必要に応じてACMなり自己証明書なりを準備しておいてください。
Amazon Linuxの準備
起動
Gucamole用のEC2を作成します。Amazon Linuxで構築します。
AMI: 最新のAmazonLinux2
インスタンスタイプ: t3.micro
VPC: Gucamoleを構築するVPCを設定
Subnet: 上記のVPCのPublicSubnet
SecurityGroup: 8080がALBから疎通可能となるよう設定
起動が完了したら作成したターゲットグループにアタッチしてください。なお、この時点ではhealthyになりません。
Dockerインストール
次にDockerをインストールします。
以下のコマンドを実行してdockerとdocker-composeをインストールします。
dockerのインストール
sudo yum install -y docker sudo service docker start sudo usermod -a -G docker ec2-user sudo systemctl enable docker
docker-composeのインストール
sudo curl -L https://github.com/docker/compose/releases/download/1.24.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose
gucamoleのイメージ構築
guacamole(gucamole-client)のイメージは公式からDocker Hubでバージョン1.1.0提供されていますが、1.1.0にはMFAの設定が組み込まれていません。そのため、githubから最新のコードをチェックアウトして自分でイメージのビルドを行います。
以下のコマンドを実行してイメージを作成します。
ビルドにはかなりの時間がかかりました。
git clone https://github.com/apache/guacamole-client.git
cd guacamole-client
docker build -t guacamole_120 .
docker-composeの作成
Dockerで構築する場合、以下の3つのコンテナが必要となります。
- guacamole
- guacd
- mysql(or postresql)
以下の内容でdocker-compos.yamlを作成します。
$SIMPLE_AD_IPには作成したSimpleADのIPを設定します。
LDAP_USER_BASE_DN、LDAP_CONFIG_BASE_DNのDCにはSimpleAD作成時に設定したドメインを設定してください。
version: "3" services: guacd: image: guacamole/guacd:latest expose: - "4822" environment: - GUACD_LOG_LEVEL=debug guacamole: image: guacamole_120 links: - guacd:guacd ports: - "8080:8080" environment: - GUACD_HOSTNAME=guacd - LDAP_HOSTNAME=$SIMPLE_AD_IP - LDAP_PORT=389 - LDAP_ENCRYPTION_METHOD=none - LDAP_USER_BASE_DN=CN=Users,DC=guacamole,DC=local - LDAP_USERNAME_ATTRIBUTE=CN - LDAP_CONFIG_BASE_DN=CN=Users,DC=guacamole,DC=local - TOTP_ENABLED=true - MYSQL_HOSTNAME=mysql - MYSQL_DATABASE=guacamole - MYSQL_USER=guacamole - MYSQL_PASSWORD=guacamole mysql: image: mysql/mysql-server:5.7 volumes: - "./mysql/data:/var/lib/mysql" - "./mysql/init:/docker-entrypoint-initdb.d" environment: - MYSQL_DATABASE=guacamole - MYSQL_USER=guacamole - MYSQL_PASSWORD=guacamole
ディレクトリの作成
docker-compose.yamlファイルと同じ階層に以下のディレクトリを作成します。
mkdir -p mysql/data mkdir -p mysql/init
tree -d mysql/ mysql/ ├── data └── init
初期データ作成
以下のコマンドで初期化用のスクリプトを作成します。
docker run --rm guacamole_120 /opt/guacamole/bin/initdb.sh --mysql > mysql/init/initdb.sql
起動
docker-compose up -d
Gucamoleの使い方
接続用のWindowsServerについては事前に用意し、Gucamoleサーバーからポート3389で疎通できるようにしておいてください。
ログイン画面にアクセス
カスタムドメインを設定していなければ、以下のいずれかのURLでアクセスしてください。
https://ELB_DNS/guacamole/
http://ELB_DNS:8080/guacamole/
正常に起動されていれば以下のようなログイン画面が表示されます。もし表示されていなければ、ログを確認してみてください。
以下のユーザー、パスワードでログインできます。
ログインユーザー: guacadmin
パスワード: guacadmin
MFA設定
MFAが有効になっていますのでGoogle Authenticatorなどで設定してください。
初期画面
以下のような初期画面が表示されます。
接続設定の追加
右上のユーザー名から「設定」を選択し、「接続」タブを開いて「接続の追加」をクリックします。
以下の通り入力を行い保存します。
接続情報の選択
右上のユーザー名からホーム画面に戻ると、先ほど登録した接続設定が表示されていますので、登録した接続情報を選択します。
RDP接続
以下のようにリモートデスクトップが開きます。
RDPの切断
切断するにはMacの場合は、「Ctrl + Shift + Option」でサイドバーが表示されますので「切断」を選択してください。
他のユーザーが利用中の場合
また、別ユーザーが接続中の場合、以下のように利用中であることが分かります。ただし、日本語表記はバグのためが表示されないため、英語表記とする必要があります。日本語だと他にも微妙な動作があったりもしたので基本は英語表記の方がいいかもしれません。
なお、接続設定において最大接続数を1とすると他のユーザーが利用中の場合は接続できないような制限をかけることが可能です。
MFAの再設定
多要素認証の機能を利用するので、スマホの故障や紛失による再設定をどのように対応するのか調べてみました。現状では管理者がUIから設定する方法はなさそうです。
そのため、DBを直接いじる必要があるようです。
以下のSQLで対象のユーザーIDを特定し
select gu.user_id from guacamole_entity ge inner join guacamole_user gu on ge.entity_id = gu.entity_id where ge.name='guacadmin'
対象ユーザーの「guac-totp-key-confirmed」属性をfalseに戻すことで、MFAの再設定が可能となります。
update guacamole_user_attribute set attribute_value='false' where attribute_name='guac-totp-key-confirmed' and user_id=${USER_ID}
Workspacesについて
検証しながら気が付いたのですが、この接続の仕組みをWorkSpacesでも利用できることに思い至りました。
WorkSpacesについては、そもそも業務都合上、利用者が自宅でも接続できる必要があるため、IP制限ができず専用のクライアントツールがあればどこからでもユーザー、パスワードでログイン可能となることが悩みとしてありました。
そのため、WorkSpacesについてもGucamole経由でアクセスすることで認証をMFAを使ってセキュアにすることができかつアクセス状況もトレースが可能となります。
設定方法の詳細は割愛させていただきますが、WorkSpacesに割り当てられているENIにアタッチされたセキュリティグループにGucamoleサーバーから3389で疎通可能となるルールを設定することで、RDPで接続が可能となります。さらにIPアクセスコントールでルールなしの状態で設定すると、クライアントツールからは接続ができません。ただし、この方法は実行モードが「AutoStop」モードでは利用できないので注意してください。