toioで遊んでみた

こんにちは。カヤックアキバスタジオでエンジニアをやっている臼井です。

この記事は 面白法人グループ Advent Calendar 2022 の6日目の記事です。

今回は気になっていたtoioで遊んでみたお話をします。 toio.io

はじめに

何故toioで遊ぶ事になったかと言うと。
弊社ディレクターから、エンジニアたるものtoioくらい知っておきなさい! と言われ、
個人的にプログラミング教育に興味があり、toio自体は知っていたので時間が出来たら触ってみようかなくらいに
思っていたのですが、気づいたらtoioが家に届いていました・・・(何故?

そう、ディレクターが送ってきたのです。(行動早ッ!)

それがこちら

まさかのバリューパック!
というわけで、せっかくなので開封して遊んでみました。

遊んでみる

中身を確認

バリューパックは、コアキューブ以外に、トイオ・コレクションが付属されていました。
トイオ・コレクションには5つのタイトルが入ってて、プログラミングをしなくてもすぐに遊べるようになっています。
コアキューブも2個あって、友達とも遊べる親切設計ですね。 toio.io

準備

複雑な設定とかはなく、本体(toioコンソール)と電源アダプター、toioリング、カートリッジを
差し込んで電源を入れ、遊びたいタイトルを選択するとコアキューブの接続が開始され直ぐに遊べました。
(wiiリモコンみたいで懐かしい)

toioリングで操作したものがこちらです。
操作するコアキューブは、toioリングのLEDと同じ色のコアキューブが操作できるようになっていました。
見てわかりやすいようにしてあって親切な作りを感じますね。

フィンガーストライク

動いてるコアキューブにおはじきを当てて相手の陣地に到達させたら勝利というシンプルな遊びです。
最初はコアキューブの移動もゆっくりで狙うのも簡単ですが、
段々早くなり、おはじきを上手く当てられなくなってきます。
コアキューブの移動速度が変わる事で難しくなる簡単な作りですがとてもいいバランスでした。

一人プレイ用もあったので遊んでみましたがシュールな感じに・・・。

リズム & ゴー

音楽に合わせてコアキューブを指定された色に移動させるとクリアという遊びです。
最初は覚える色も1つで簡単で物足りなさもあったのですが、レベルが上がっていくごとに指定される色が増え、
コアキューブの向きも考慮しながら移動させるとなると、いい頭の体操になりました。

リズム&ゴーは操作用パネルをコアキューブで読み取って操作する遊びで、
残り3つのタイトルも付属の物を使って遊ぶ形になっていました。
コアキューブがパネルに書かれている情報を解釈して遊ぶことができるのは凄いですね!
全部紹介すると記事が長くなる為、今回はこの辺りで。

こちらが付属されていた物です。

toio SDK for Unity

toio SDK for Unityはコアキューブを制御するための開発環境が入ったUnityPackageです。
サンプルやチュートリアルも豊富で、Visual Scriptingも対応されていたので、
プログラミングがわからない方でも気軽に試せそうな感じでした。

Unityで何かやってみたいけど何をしたらいいかわからない・・・。 という方は
toio SDK for Unityを使って何か作ってみるのもいいかもしれませんね。

morikatron.com

今回はあまり時間が取れなかったので、sdkで提供されていたサンプルを
iPhoneに転送して確認したものを貼っておきます。

まとめ

Advent Calendarのネタを探してるタイミングで、ひょんなことからtoioに触れる機会が出来ました。
トイオ・コレクションも面白く甥っ子たちと一緒に遊んでみようと思います。
(toio SDK for Unityを使って作った物を一緒に遊べるといいな)
バリューパックと記事のネタを提供してくれたディレクターには感謝ですね!

ecspresso v2とTerraform null_resourceで一発構築

SREチームの藤原です。

この記事はTech KAYAC Advent Calendar 2022 5日目の記事です。

この記事では筆者が開発しているAmazon ECSデプロイツール ecspresso (v2)と、Terraformnull_resourceを組み合わせて、 TerraformによるECS関連リソース作成とecspressoによるECSサービスのデプロイを一発で実行する手法について説明します。

ecspresso とは

github.com

ecspressoは筆者(fujiwara)が開発している、Amazon ECS用のデプロイツール(OSS)です。ECSにタスク定義とサービスをデプロイするツールはAWSが作っているものを含めて世の中に多々ありますが、ecspressoは次のような特徴を持っています。

  • Go 言語で書かれた OSS (MIT LICENSE) です
  • ECS サービスとタスクに関わる最小限のリソースをコード管理し、デプロイを実行するためのツールです
    • ECS サービスとタスクが動作するにあたって必要な関連リソース(例: IAM Role, ELB target group, VPC, Subnet, Security gruop など)を作成/管理する機能はありません
  • Terraform の State を管理している tfstate ファイルを読み、その情報を使うことができます
  • 既存の ECS サービスとタスクの情報を元に、構成ファイルを生成する機能があります
    • AWS コンソールや他のツールでデプロイしている既存サービスを、あとから ecspresso で管理するように変更できます

詳しくはecspresso handbookの1章(無料公開)も参照して下さい。 zenn.dev

ecspressoは、基本的にECSのタスク定義とサービスしか管理しないため、ECSが動作する際に必要な他のAWSのリソース (IAM RoleやVPC、ロードバランサーなど)は、別の手段で管理する必要があります。 *1

Terraformと組み合わせる

ECSサービスが必要とする関連リソースについては、カヤックでは主にTerraformによる管理を行っています。

ecspressoにはサービス定義やタスク定義ファイルの中でtfstate(Terraformが構成しているリソースの情報を保持しているファイル)を参照して、その属性を展開する機能があります。

{
  "launchType": "FARGATE",
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "securityGroups": [
        "{{ tfstate `data.aws_security_group.http.id` }}"
      ],
      "subnets": [
        "{{ tfstate `aws_subnet.az-a.id` }}",
        "{{ tfstate `aws_subnet.az-c.id` }}"
      ]
    }
  }
}

このように記述すると、実際にTerraformによって構築されたリソースのIDをハードコードすることなく、Terraformのコード上で管理しているリソース名で記述できます。

環境ごとにtfstateを分割して同一のリソース名にしておくことで、参照するtfstateを切り替えるだけで複数環境へのECSデプロイを統一的に管理することもできます。

ecspresso + Terraform構成の面倒なところ

ECSへのアプリケーションのデプロイと、ECSが要求する依存リソースは更新のサイクルが異なることが多いため、長期運用する場合や管理するチームが分かれている場合などは特に、2つのツールを使い分けることにメリットがあります。

しかし、例えば検証用の環境を1から立ち上げて不要になったらすぐに削除したい、というような場合には1つのツールで完結するほうが便利です。

また、ECSが依存するリソース(例: IAM Role)と、ECS依存するリソース(例: Application Auto Scaling)を両方1つのTerraformで管理しようとすると、terraform applyを1回で済ませることができません。

  1. TerraformでIAM Roleを作成
    • この時点ではECSサービスはないため、Application Auto Scalingはコメントアウトするなどして作成しないようにする
  2. ecspressoでECSサービスを作成
  3. TerraformでApplication Auto Scalingを作成

と順に手作業をする必要があり、これが面倒なところでした。

ecspresso v2とTerraform null_resourceによる一発構築手法

ところでecspressoでは、もうすぐv2をリリースする予定です。v2での変更点は、以下の記事を参照して下さい。

sfujiwara.hatenablog.com

v1ではECSサービスを新規に作成する場合にはecspresso create、作成済みのサービスに変更やデプロイを行う場合にはecspresso deployと、サブコマンドを使い分ける必要がありました。

この仕様は冪等性がなく不便なことが多かったため、v2では新規作成も更新もecspresso deployコマンドのみで実行できるようになりました。

そして、この挙動とTerraformのnull_resourceを組み合わせることで、terraform applyを1回実行するだけで関連リソースとECSへのデプロイが完結できるようになりました!

具体的には、次のようなことが可能です。

  • 関連リソースの初期構築とECSへのデプロイをterraform applyのみで行える
  • Terraformで管理しているリソースに変更があった場合もterraform applyからecspresso deployが呼ばれるため自動でデプロイされる
    • ECSに関連しないリソースの変更ではデプロイは発生しません
  • もちろん単独でecspresso deployすることも可能
  • terraform destroyするとECSサービスも関連リソースも削除される

null_resourceとは

The null_resource resource implements the standard resource lifecycle but takes no further action.

とあるように、それ自身はTerraformリソースとしてのライフサイクルを持ちますが、単体では「何もしない」リソースです。

ただし、他のリソースの変更をトリガーにしてprovisionerでTerraformから外部コマンドを実行できます。そこでecspressoを実行することで、ECSサービスが依存するリソースも、ECSサービス自身も、ECSサービスに依存するリソースも、terraform apply一撃で構築できますし、terrraform destroy一撃で削除できます。

具体例

Terraformで管理するものは以下とします。

  • ECSが依存するリソース
    • IAM Role (タスク実行ロール)
    • ECSクラスタ
  • ECSに依存するリソース
    • Application Auto Scaling target

ECSが依存するリソースの.tfは普通に記述します。

resource "aws_iam_role" "ecs-task-execution" {
  name = "ecs-task-execution-oneshot"
  assume_role_policy = data.aws_iam_policy_document.ecs.json
}

resource "aws_iam_role_policy_attachment" "ecs-task-execution" {
  role       = aws_iam_role.ecs-task-execution.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

resource "aws_ecs_cluster" "oneshot" {
  name = "oneshot"
}

null_resourceによってecspressoでデプロイする記述は次のようになります。

  • triggersにECSが依存するリソースを記述
  • provisioner "local-exec"ecspresso deployを実行する
    • environmentで依存リソースの値を渡す
  • when = destroy条件でECSタスクを0にしてサービスを削除する記述も

ポイントは「environmentで依存リソースの値を渡す」点です。最初にterraform applyを実行してる途中にはtfstateに値が書き込まれていないため、ecspresso側からtfstate参照でIDなどを解決できません。しかしterraformの中では実行中でも作成済みのリソースの値は参照できるため、それを環境変数でecspresso側に渡してあげることで解決します。

// ECSが依存するリソースの変更でトリガーされるnull_resource
resource "null_resource" "ecspresso" {
  triggers = {
    cluster            = aws_ecs_cluster.oneshot.name,
    execution_role_arn = aws_iam_role.ecs-task-execution.arn,
  }

  provisioner "local-exec" {
    command     = "ecspresso deploy"
    working_dir = "."
    environment = { // 環境変数で依存リソースの値(ecspressoで参照するもの)を渡す
      ECS_CLUSTER        = aws_ecs_cluster.oneshot.name,
      EXECUTION_ROLE_ARN = aws_iam_role.ecs-task-execution.arn
    }
  }

  provisioner "local-exec" {
    command     = "ecspresso scale --tasks 0 && ecspresso delete --force"
    working_dir = "."
    when        = destroy // terraform destroy時に発動する条件
  }
}

ecspressoが使用するタスク定義は、次のようにします。ポイントは「 環境変数があればその値、なければtfstateを参照する」ためにテンプレート関数を{{ or }}を使って記述することです。

terraform applyによってnull_resourceがトリガーされて実行される場合は環境変数が使われます。Terraform経由ではなくecspresso deployを単独で実行した場合は、tfstateを参照することになります。

{
  containerDefinitions: [{
    essential: true,
    image: 'nginx:latest',
    name: 'nginx',
  }],
  cpu: '256',
  executionRoleArn: '{{or (env `EXECUTION_ROLE_ARN` ``) (tfstate `aws_iam_role.ecs-task-execution.arn`)}}',
  family: 'nginx',
  memory: '512',
  networkMode: 'awsvpc',
  requiresCompatibilities: ['FARGATE'],
}

最後に、ECSサービスに依存するリソースを記述します。data resourceでecspressoで管理しているサービスを参照し、それに依存する形でApplication Auto Scaling targetを定義しています。

data "aws_ecs_service" "oneshot" {
  cluster_arn  = aws_ecs_cluster.oneshot.name
  service_name = "nginx"
  depends_on = [
    null_resource.ecspresso,
  ]
}

resource "aws_appautoscaling_target" "nginx" {
  max_capacity       = 10
  min_capacity       = 1
  resource_id        = "service/${aws_ecs_cluster.oneshot.name}/${data.aws_ecs_service.oneshot.service_name}"
  scalable_dimension = "ecs:service:DesiredCount"
  service_namespace  = "ecs"
}

実行結果の例

terraform applyで一撃構築されている様子がこちらです。IAM Role、ECS Cluster、ecspresso deploy、Application Auto Scaling targetの順で作成されていることが分かります。

aws_ecs_cluster.oneshot: Creating...
aws_iam_role.ecs-task-execution: Creating...
aws_iam_role.ecs-task-execution: Creation complete after 2s [id=ecs-task-execution-oneshot]
aws_iam_role_policy_attachment.ecs-task-execution: Creating...
aws_iam_role_policy_attachment.ecs-task-execution: Creation complete after 0s [id=ecs-task-execution-oneshot-20221202070518071000000001]
aws_ecs_cluster.oneshot: Still creating... [10s elapsed]
aws_ecs_cluster.oneshot: Creation complete after 11s [id=arn:aws:ecs:ap-northeast-1:314472643515:cluster/oneshot]
null_resource.ecspresso: Creating...
null_resource.ecspresso: Provisioning with 'local-exec'...
null_resource.ecspresso (local-exec): Executing: ["/bin/sh" "-c" "ecspresso deploy"]
null_resource.ecspresso (local-exec): 2022/12/02 16:05:26 nginx/oneshot Starting deploy
null_resource.ecspresso (local-exec): 2022/12/02 16:05:27 nginx/oneshot Service nginx not found. Creating a new service
null_resource.ecspresso (local-exec): 2022/12/02 16:05:27 nginx/oneshot Starting create service
null_resource.ecspresso (local-exec): 2022/12/02 16:05:27 nginx/oneshot Registering a new task definition...
null_resource.ecspresso (local-exec): 2022/12/02 16:05:27 nginx/oneshot Task definition is registered nginx:19
null_resource.ecspresso (local-exec): 2022/12/02 16:05:27 nginx/oneshot Service is created
null_resource.ecspresso (local-exec): 2022/12/02 16:05:30 nginx/oneshot Waiting for service stable...(it will take a few minutes)
null_resource.ecspresso: Still creating... [10s elapsed]
null_resource.ecspresso (local-exec): 2022/12/02 16:05:31 (service nginx) has started 1 tasks: (task 911019d2120b41ffa3b50da400cf5cb3).
null_resource.ecspresso (local-exec): 2022/12/02 16:05:40 nginx/oneshot  PRIMARY nginx:19 desired:1 pending:1 running:0
null_resource.ecspresso: Still creating... [20s elapsed]
null_resource.ecspresso (local-exec): 2022/12/02 16:05:50 nginx/oneshot  PRIMARY nginx:19 desired:1 pending:0 running:1
null_resource.ecspresso: Still creating... [30s elapsed]
null_resource.ecspresso (local-exec): 2022/12/02 16:06:06 nginx/oneshot Service is stable now. Completed!
null_resource.ecspresso: Creation complete after 39s [id=7075482505817107284]
data.aws_ecs_service.oneshot: Reading...
data.aws_ecs_service.oneshot: Read complete after 0s [id=arn:aws:ecs:ap-northeast-1:314472643515:service/oneshot/nginx]
aws_appautoscaling_target.nginx: Creating...
aws_appautoscaling_target.nginx: Creation complete after 1s [id=service/oneshot/nginx]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

まとめ

ecspresso v2 とTerraform null_resourceを組み合わせることで、関連リソースもECSサービスも1コマンドの実行で構築/削除が完結する手法を紹介しました。

ecspresso は現在 v1.99.6をプレリリースしています。 試していただけると嬉しいです。

*1:この設計には意図があります。ecspresso handbookの設計思想と実装 の章を参照して下さい