TECH PLAY

KINTOテクノロジーズ

KINTOテクノロジーズ の技術ブログ

936

この記事は KINTOテクノロジーズアドベントカレンダー2025 の13日目の記事です🎅🎄 はじめに はじめまして!の方も、いつもありがとうございますの方もこんにちは!! KINTOテクノロジーズ(以下、KTC)でエンジニア採用と採用広報を担当している たけの ひかる( @t_hikarutaaaan )です。 毎年恒例のアドベントカレンダー企画、 今年も呼吸をするようにエントリーしてみました!(笑) 今回は、 「エンジニア採用と採用広報の舞台裏」 をテーマに、 私の “とある1日” をゆるっと紹介してみようと思います。 すこしだけ自己紹介 入社して約4年、採用担当になって約2年。 当時は 人事も採用も完全にゼロ経験からのスタート。 技術の話は半分も理解できず、 スカウト1通送るだけであたふたして、 面談ではガチガチに緊張してスクリプトを棒読み。(今では完全にネタw) でも、止まらずに挑戦し続けたからこそ今言えます。 「採用という仕事が、本当に好きです。」 そしてKTCは、 “やってみたい” と手を挙げた人に本気で任せてくれる会社。 未経験の私が走り続けられたのは、そのカルチャーのおかげです。 やっと笑ってイベント司会ができるようになりました エンジニアって、魔法使いじゃん 実は、採用担当になって少し経ったころ、生成AIにコードを書いてもらいながら 採用データを分析できる小さなアプリをつくったことがあります。(アプリって言っていいレベルではないですがw) ワンクリックで画面にグラフが表示された瞬間、思わず声が出ました。 「え、すご…魔法じゃん。」 技術が人の困りごとを一瞬で解決してしまう瞬間にシンプルに感動しました。 で、そのとき思ったんですよね。 「あ、もっとちゃんと向き合わなきゃ」って。 現場の人たちがどんな想いでプロダクトつくってるのか、 「もっとちゃんと知りたいし、もっともーっとちゃんと伝えたい」と。 そこから更に毎日あたふたしながら走り回ってます。(笑) というわけでここからは、 そんな私の 「とある1日」 をゆる〜く紹介してみます! 時間 内容 実際にやったこと こんな気持ちでやってます(例) 09:00 スカウトtime ターゲット選定、プロフィール読み込み、スカウト文作成&送付 どの媒体にしようかな。良い人発見。「なぜ声をかけたいのか」を必ず書く 10:00 書類選考 職務経歴確認、現場メンバーと評価すり合わせ むむ…めちゃくちゃ良い人…! 11:00 現場MTG 採用状況共有、採用要件摺り合わせ、PJ状況ヒアリング 分からないことは素直に聞く。優しく答えてくれる文化に感謝 🙏 12:00 ランチtime 卵かけごはんをすする かつお節が合う。おなかいっぱい。 13:00 カジュアル面談 キャリアヒアリング、プロダクト紹介 双方コミュニケーションできた時が最高。時間足りない日も多い。 15:30 採用広報企画 採用トレンド調査、競合リサーチ、企画案作成、資料構成 煮詰まった…。壁打ち相手求む。 17:00 カジュアル面談 キャリアヒアリング、プロダクト紹介 技術質問に少し答えられず…あとで現場に確認。 19:00 イベント運営 受付、撮影、アナウンス、来場者サポート 基本、走り回ってます(笑)。写真撮って声かけて…ハイハイ! コスパ&タイパ最高の卵かけごはん 採用の難しさと向き合う日々 採用は、スペックだけでは測れない世界です。 会話の空気 ちょっとした表情の変化 選択の背景にある価値観 そういった“見えないもの”を丁寧に受け止める必要があります。 ある候補者の言葉が忘れられません。 「転職の決め手は、“誰と働くか”なんです」 その一言で、ハッとしました。 私はずっと会社としての魅力を伝えることに精一杯でした。 でも候補者の方が本当に知りたいのは、 「このチームなら、自分の挑戦を託せるか」 それからは、 現場の空気やカルチャー、そこで生じる悩みや挑戦も含めた本音を届けることを大切にしています。 面談の最後に 「一緒に働く姿が想像できました」 と言ってもらえた日は、心の中で小さくガッツポーズしています。(笑) 採用広報で挑戦したこと 今年、採用広報として 国内最大級のモビリティイベントである Japan Mobility Show 2025 の登壇企画を担当しました。 KTCが所属するTOYOTAグループ内の関係者や、 リアル領域とIT領域それぞれの担当者を巻き込みながら進めるプロジェクトで、 調整量も難易度も、正直これまでで一番大変でした。(笑) でも、どうしても実現したかった理由があります。 私たちの強みである「リアル × IT」での挑戦を、ちゃんと外に届けたかった。 そして、「一緒にやろう」と快諾してくれた部長の熱量を発信したかった。 求人票だけでは伝わらない空気や想いを、 生の言葉で届けたかったんです。 本番のあと、応援してくれていた社内メンバーから 「めちゃくちゃよかったよ!」 と声をかけてもらえて、すごく嬉しかったです。 さらに後日、登壇企画の動画が公開されたあと、 日々いろいろな業務で関わるグループ企業の方々にも 「YouTube見たよ!」「いい内容だった」「かっこよかった」 と声をかけてもらえたと。 さらに、登壇してくれた部長はこんな言葉をくれました。 「採用に効いてくるのはもう少し先かもしれないけど、 社内広報とか、KTCのセルフブランディングには効いてくるね」 めちゃめちゃ嬉しかったぁ。なんだろ。うまく言葉にできないけど(笑) 採用広報は、会社の挑戦と未来の仲間をつなぐ仕事。 私はその“橋渡し役”でありたいと思っています。 会場の写真を一生懸命撮ってます ちなみに以下がこの記事で触れた登壇企画のアーカイブ動画です! あつーーく、ギュギュっと詰まった50分なので是非ご視聴ください♪ https://youtu.be/NXM2lyapia0?si=QiK5GP2XFtCh0c9c 採用はチーム戦であり、未来づくり 採用に、これが正解!っていう形はありません。 毎回迷うし、悩むし、揺れ続けます。 (正直、答えが分からなすぎてソワソワする日もある。笑) でも、ひとつだけ確信していることがあります。 いい採用は、いい“対話”からしか生まれない。 KTCには、私を含めて5名の採用担当がいます。それぞれが部門に専任としてつく部門担当制。 だからこそ、現場とめちゃくちゃ密にコミュニケーションができるんです。 プロダクトの話も、カルチャーの話も、 嬉しいことも、しんどいことも、ぜんぶ一緒に考える。 そうやって、チームで採用をつくっている感覚がある。 これは本当に誇れるポイントだと思っています。 最後に、読者のみなさまへ エンジニアのみなさまへ   尊敬しています。本気で。   技術で未来を変えていく姿は、私にとってずっと魔法のように見えています🪄 プロダクト開発に関わるみなさまへ   本気でプロダクトと向き合う姿勢や、ひとつの価値をつくるためのコミュニケーションや試行錯誤。   そのリアルなストーリーに、私はいつも心を動かされています。 人事グループの仲間へ   いつもわちゃわちゃしている私と一緒に悩んでくれて感謝です!!   これからも一緒に走ってね!!(/・ω・)/ KTCのみなさまへ   いつも助けてくれてありがとうございます!!   私の大好きな会社がもっと強くなるために、まずは採用という場所から全力で貢献します!!! この記事を読んでくださったみなさまへ   もし少しでも何か響くものがあったら、   ぜひイベントで、カジュアル面談で、、、、どこかでお会いできたら嬉しいです!! 未来の仲間へ 私たちKINTOテクノロジーズは変革期の自動車産業のど真ん中で、 内製開発組織をゼロからつくる挑戦をしています。 こんなダイナミズムに関われるチャンスは、そう多くありません。 未来を一緒に創る仲間をお待ちしています🙌 以上、採用担当・広報担当の“とある1日”でした! 読んでくださり、本当にありがとうございました~~~!
アバター
はじめに この記事は KINTOテクノロジーズ Advent Calendar 2025 の12日目の記事です🎅🎄 こんにちは。クラウドインフラ所属の松尾です。 AWS re:Inventで発表された新機能ではないですが、 個人的に熱いと感じたアップデートがあったので紹介します。 Service Quotasの自動管理機能です。 2025年10月に「自動管理設定」機能がGAとなり、 Service Quotasの上限値が近づくと通知が飛ぶようになりました。 そして先日、GAのタイミングで予告されていた「自動調整」モードが追加。 これまでクォータ監視を実装しようとすると、複雑な構成を自前で用意する必要がありましたが、 この機能を使えばコンソールからポチポチするだけでクォータの監視から自動調整までやってくれます。 しかも無料です。 ただ、先に言っておくと自動調整について、「全部のクォータに対応しているわけではない」という制約があります。 この記事では、実際に検証してわかった仕様や制約を書いていきます! アップデートの経緯 日付 内容 2025年10月 自動管理機能の追加。追加時は「通知のみ」モードがGA。自動調整モードの予告あり 2025年11月 「通知と自動調整」モードが追加 公式アナウンス: AWS Service Quotas の自動クォータ管理の一般提供を開始 - AWS AWS Service Quotas now supports automatic quota adjustment 何が嬉しいのか Before(従来の方法) クォータ監視を実装するには、こんな構成が必要でした: https://aws.amazon.com/solutions/implementations/quota-monitor/ 出典: AWS Solutions Library CloudWatch Alarmsでメトリクスを監視 Lambda関数でService Quotas APIを叩いて上限緩和リクエストを送信 EventBridgeでトリガー設定 SNSで通知 Amazon DynamoDB 監視対象のクォータの管理 これだとかなり複雑な構成になるので構築、運用が大変でした。 After(自動管理機能) コンソールで数クリックで設定完了 80%/95%で自動通知 「自動調整」モードなら自動で上限緩和リクエストまで送ってくれる 今までクォータ監視で必要だったリソースの料金の発生はなし 従来までの方法と比べ、複雑な設定は無し。かなり導入がしやすそうです。 Before After 設定の手間 Lambda/EventBridge/SNS/DynamoDB等の構築が必要 コンソールで数クリック 通知タイミング 自分で閾値を設定 80%/95%で自動通知(固定) クォータ調整 Lambda等で自前実装 自動調整モードで対応 料金 各リソースの料金が発生 無料 機能の仕様 通知タイミング 公式ドキュメント によると、以下のタイミングで通知が送信されます: 80% 使用率に達した時 95% 使用率に達した時 重要:この閾値は固定です。 コンソールで設定するとき「あれ、閾値設定するところないな?」って思ったんですが、そもそも変更できない仕様でした。 「70%で通知が欲しい」みたいなカスタマイズはできないので、その場合は従来通りCloudWatch Alarmsを使う必要があります。 自動管理モード モード 説明 通知のみ(NotifyOnly) 80% / 95%で通知を送信 通知と自動調整(NotifyAndAdjust) 通知に加えて、自動でクォータ増加リクエストを送信 通知先 AWS User Notificationsと統合されていて、以下の通知先を設定できます: Eメール(SES経由、サポートされていないリージョンからのイベントはバージニア経由で送信) AWS コンソールモバイルアプリケーション(事前にアプリのインストールとプッシュ通知の有効化が必要) チャットチャネル(Amazon Q Developer経由でSlack/Teamsに配信、事前にチャットクライアントの設定が必要) 料金 無料で使用できます。 自動管理機能自体には追加料金はかかりません。 通知に使われるAWS User Notificationsも無料ですが、 裏で呼び出しているSES・モバイルプッシュ・Amazon Q Developerの使用や、 自動管理機能とは別にCloudWatch Alarmsで独自の監視を設定する場合は、別途料金がかかる場合があります。 設定方法 📝 補足 : 2025年11月時点では本機能は現在Terraformでは対応しておりません コンソールでの設定 Service Quotasコンソールを開く 左メニューから「自動管理」を選択 「自動管理を開始」をクリック 自動管理モードを選択(「通知のみ」または「通知と自動調整」) 通知設定を構成(オプション) 例外設定を構成(オプション) 確認して送信 めちゃくちゃ簡単。 CLIでの設定 以下はus-west-2(オレゴン)リージョンでの設定例です。 # User Notificationsの通知先と自動調整を指定する場合 aws service-quotas start-auto-management \ --opt-in-level ACCOUNT \ --opt-in-type NotifyAndAdjust \ --notification-arn arn:aws:notifications::xxxxxxxxxxxx:configuration/xxxxx \ --region us-west-2 公式ドキュメント ⚠️ 注意 : リージョンごとに設定が必要です。オレゴンリージョンで設定しても、東京リージョンには適用されません。 CLIで全リージョン設定する場合はスクリプトを組むなどの対応が必要です。 内部の仕組み(イベントパターン) 自動管理を設定すると、内部的にはAWS User Notificationsの詳細フィルターとして以下のようなイベントパターンが設定されます: { "source": ["aws.health"], "detail-type": ["AWS Health Event"], "detail": { "service": ["SERVICEQUOTAS"], "eventTypeCode": [ "AWS_SERVICEQUOTAS_THRESHOLD_BREACH", "AWS_SERVICEQUOTAS_INCREASE_REQUEST_FAILED", "AWS_SERVICEQUOTAS_APPROACHING_THRESHOLD" ], "eventTypeCategory": ["accountNotification"] } } 各イベントタイプの意味( 公式ドキュメント ): eventTypeCode 意味 AWS_SERVICEQUOTAS_APPROACHING_THRESHOLD 調整可能な クォータが閾値に近づいた。自動調整モードならここでクォータの引き上げリクエストが送信される AWS_SERVICEQUOTAS_THRESHOLD_BREACH 調整できない クォータが閾値を超過。クォータの引き上げできないので使用率を最適化する必要あり AWS_SERVICEQUOTAS_INCREASE_REQUEST_FAILED クォータの引き上げリクエストが失敗した 実際に検証してみた 検証環境 リージョン:us-west-2(オレゴン) 自動管理モード:通知と自動調整 必要な権限 自動管理機能を使用するために必要な権限、および閲覧権限は以下の通りです。 使用するために必要な権限 ServiceQuotasFullAccess AWSHealthFullAccess 閲覧するために必要な権限 AWSHealthFullAccess 対応クォータの確認 コンソールの「サポートされているクォータを表示」から確認してみたんですが、リリース直後だからか思ったより対応範囲が限定的でした。 今後、対応するリソースが増えることを期待したいです。 対応していたサービスの例: AWS Systems Manager Amazon EC2 WAF(Regionalのみ) ELB Lambda Route53 IAM 対応していなかったサービスの例: VPC API Gateway EventBridge Amazon Simple Email Service(SES) Route53やIAMなどのグローバルサービスの場合、通知のみできるようで自動調整には対応していない印象でした 自動調整の動作確認 AWS WAFの「Maximum regex pattern sets per account in WAF for regional」で検証してみました。 このクォータはデフォルト値が10なので、8個作成すれば80%に到達します。 検証手順 1. 事前準備 まずはService Quotasで自動管理の設定を行います。 Service Quotasの自動管理を「通知と自動調整」モードで有効化 (オレゴンリージョンで設定した際、キャプチャしてなかったのでバージニアリージョンでの設定画面になりますmm) 通知設定に移ります。ここではAWS User Notificationsの設定を行います。 今回はAmazon Q Developerを用意し、Slackチャンネルに通知するようにしました。 ⚠️ 注意 : イベントソースのリージョンを選択できますが、自動管理を有効にしているリージョンのイベントしか受け取れないため、 監視したいリージョンごとに自動管理の設定が必要です(自動管理とAWS User Notificationsは別設定だからちょっとややこしい) 自動管理の例外とするサービスを選択できますが、今回は何も設定しないまま作成 2. 正規表現パターンセットを作成 次に正規表現パターンセットを作成し、閾値に引っかかるようにします。 WAFコンソール → 正規表現パターンセット → 「Create regex pattern set」 リージョン:us-west-2(オレゴン)※Regionalを選択 適当な名前で8個作成 3. 使用率の確認 Service Quotas → AWS WAF → 「Maximum regex pattern sets per account in WAF for regional」 使用率が80%になっていることを確認しました 4. 通知/自動調整の発動を待つ 数分待機... 検証結果 通知/自動調整反映までの時間:正確には確認できていませんが、数分程度で完了 クォータ値:10→11に。自動調整後のクォータ値の指定はできませんが、80%を切るようなクォータに更新してくれそう? 通知に関して、Slack通知は見づらい 改行コード( \n )がそのまま表示されてしまい、長文が一塊になっています。 また、通知本文には「どのクォータが閾値に近づいたか」の具体的な情報が含まれておらず、 通知文に添付のリンクからAWS Health Dashboardの「Affected resources」タブを確認する必要があります。 通知としては「何か閾値に近づいた」ことはわかるが、詳細確認には一手間かかる印象です。 注意点・制約 1. 対応クォータが限定的 正直、今のところこれが一番大きな制約だなと感じました。 VPCやSESといった基本的なサービスのクォータが対応していないのは残念です。 現時点ではService Quotaの自動管理だけでは「クォータ周りの調整がすべて解決」とはならない。 対応クォータの拡大を今後待つしかないです。 2. 閾値のカスタマイズ不可 80%/95%の閾値は固定。「50%で早めに通知が欲しい」「100%になるまでは通知不要」みたいな要件には対応できません。 3. AWS Organizations対応は未実装 2025年11月時点では、Organizations単位での一括設定には対応していないため、 各アカウントで個別に設定する必要があります。 CLIコマンドには --opt-in-level というオプションがCLIであるのでいつか対応されることを期待したいです。 4. 自動調整の承認は保証されない 自動調整はあくまで「上限緩和リクエストを自動送信する」機能です。 リクエストがAWSに承認されるかどうかは別の話で、拒否される可能性もあります。 5. 通知・自動緩和までに時間がかかる場合がある クォータが11に上がった直後、正規表現パターンを3つ追加して合計11個にして使用率を100%にしてみました。 が、今度は通知もされず、自動調整もありませんでした。 しかし数時間後、自動調整されていることが確認できたことから、 場合によっては通知や自動調整までに時間がかかることがあるようです。 まとめ Service Quotasの自動管理機能は、クォータ監視のハードルを大きく下げてくれる個人的にはかなり良いアップデートです。 無料で始められて、コンソールから数クリックで設定できます。 対応クォータがまだ限定的だったり、IaC(Terraform)未対応だったりとまだまだ制約はありますが、 サポートされているクォータに関しては実際にクォータ拡張作業の工数を削減できることがわかりました。 弊社では独自実装のクォータ管理ツールも運用しており、 自動管理機能でサポートされていないクォータについては引き続きそちらで対応していく予定です。 以上、この記事が誰かの参考になれば幸いです。
アバター
この記事は KINTOテクノロジーズ Advent Calendar 2025 の12日目の記事です🎅🎄 はじめに KINTOテクノロジーズのFACTORY EC開発グループでバックエンドエンジニアをやっている、うえはら( @penpen_77777 )です。 技術書典19に、KINTOテクノロジーズの有志エンジニアによるサークル「KINTOテクノロジーズ執筆部」として参加してきました。 この記事では社内有志で技術書を作り上げるまでの道のりをご紹介します。 ちなみに今回作った新刊の紹介はこちらになります!無料なのでもしご興味があれば電子版のダウンロードよろしくお願いします。 https://blog.kinto-technologies.com/posts/2025-10-31-techbookfest19-announcement/ https://techbookfest.org/product/qCPrJpWLmKnLt7eWVd9zJ6 始まり 私はこれまでに技術本の執筆をしたことがあり、久々に技術本を書きたいなという思いがありました。 文章を他人に読んでもらうだけなら技術ブログや登壇でも十分なのですが、やっぱり自分の書いた文章が本になるのは感慨深いものがあります。 弊社では社員有志でサークルを組んで技術書を書くということがなかったため、今回チャレンジしてみることにしました。 仲間を作る サポートしてくれそうな人を見つける 技術書典へ参加する約半年前の6月くらいから動き出し始めました。 早めにサポートしてくれそうな人を見つけておくとタスクを進めておく上で非常に心強いです。 弊社には技術広報グループがあり社員の技術的な外部発信の相談に乗ってもらえます。 やり遂げることができたのも技術広報のおかげだなと思っています。本当に感謝しています。 執筆に興味がありそうな人を見つける 技術書典への参加は初めての試みということもあって、本当に参加者が集まるのかが不安なところでした。 いきなり全社に参加者募集の告知をして、参加者を集めるのはハードルが高いと感じていました。 そこで興味がありそうな人に声をかけてあらかじめ参加する確度の高い人を集めることにしました。 弊社ではSlack上でtimes(分報チャンネル)を活用する人も多く、気軽に自分の気持ちを表明できるのが良いところです。 技術書典への出展したい気持ちを匂わせることで、興味がありそうな人が誰か知ることにしました。 ここで5人くらい集まったので、最悪全社告知して集まらなくても企画ができそうだなと思い、安心して進めることができました。 告知する 全社で執筆者を募集できるよう簡単なスライドを作りました。 技術書典について、執筆に対するメリット等をまとめていて参加を訴求する内容となってます。 イベントの規模を紹介するのに公式から出ている協賛資料が参考になりました。 https://techbookfest.org/assets/tbf18/for-sponsors.pdf 印刷所の選定をする 物理本を作るため印刷所の選定が必要になります。 技術書典ではバックアップ印刷所が設定されており、このバックアップ印刷所に入稿することで印刷から会場への納品までやっていただける非常に便利な仕組みとなっています。 バックアップ印刷所には日光企画・ねこのしっぽがあるのですが、今回は日光企画さんを選択しました。 組版システムを選定する・設定を整える 組版システムとは印刷向けの見た目に変換するものを言います。 技術書向けの組版システムとしてはRe:VIEWがよく使用されます。 独自の「Re:VIEWフォーマット」で記述されたテキストファイルをRe:VIEWに通すことによって、電子書籍としてよく用いられるEPUB、PDFなどに変換されます。 紙面上のスタイルの設定と内容が分離していることで、執筆者が見た目を意識せずどんな内容の文章を書くかという本質的な営みに集中できるのがメリットです。 今回は組版システム^[読み方は「くみはん」らしいです。毎回そばんと読んでしまいます。 https://ja.wikipedia.org/wiki/%E7%B5%84%E7%89%88]として「Vivliostyle」を使用しました。 https://vivliostyle.org/ja/ VivliostyleはCSSを使って見た目を変えられるのが特徴で、フロントエンド技術に詳しい方であれば難なく使いこなせるかと思います。 ゼロから設定を整えるのは大変なので、今回はゆめみさんが配布されているテンプレートを活用させていただきました。 このようなテンプレートを配布いただき大変感謝しております。ありがとうございました! https://github.com/yumemi-inc/daigirin-template 上記のテンプレートを元に以下の修正を入れました タイトル・発行者・奥付けを今回の技術書向けの内容に修正 紙面サイズをB5へ変更^[ここでB5にはJIS B5とISO B5があることを知りました。同じB5でもサイズが異なるのでご注意を。] 目次の自動出力を有効化、うまく出力できていない箇所はテンプレートを調整 技術書典へサークル登録する 色々準備がある中で忘れそうにはなりますが、サークル参加申込は忘れずに行いましょう。 カレンダーに予定を入れたりする方が良いかもですね。 執筆者に書いてもらう 執筆者が執筆に困らないようガイドラインを作成し、共有しました。 全体的なスケジュールとそれぞれのフェーズでやること 執筆方法(Vivliostyle Flavored Markdown^[一般的なmarkdownを書籍特有の構造も表現できるよう拡張した記法。 https://vivliostyle.github.io/vfm/#/]の記法 , PDFのプレビュー方法等) 原稿管理 原稿管理はGitHubで実施し、mainブランチからそれぞれ執筆者ごとにブランチを切ってもらい執筆作業を行ってもらいました。 執筆者ごとにPRを出しておくと進捗確認ができて便利です。 Slackリストを活用する 書籍内でテーマが被らないようテーマが決まったらSlackのリストにまとめてもらうようにお願いしました。 他にも巻末に入れる自己紹介文や執筆状況等、自分で把握しておきたい内容を入れておくと見通しが良いです。 Confluenceで表を使って管理しても良かったのですが、個人的にはSlackの方が見る頻度が高いのとNotionのデータベースっぽく使えるSlackリストで管理していました。 表紙を作成してもらう 技術書の表紙を自分で作るか悩んでいたところ、参加者の一人から「クリエイティブ室のデザイナーさんに協力をお願いしてみては?」と提案がありました。 せっかくなら人を集めてワイワイしながら作っていこうということで、デザイナーが生成AIを駆使して表紙を作成する「生成AI × ライブペインティング」イベントの開催が決まりました。 今回の技術書を作るのに開催されたイベント「Vibe Painting Hour #01」 イベントでは5人のデザイナーの方に来ていただき、参加者が見守る中、1時間という制限時間で生成AIを駆使して表紙を作っていただきました。 「モビリティ感があって未来感のある表紙を作る」という、うえはらからの中身がふわふわした依頼にもかかわらず、最終的に上がってきた表紙の5案はどれもクオリティが高く1つに決めるのが勿体無いくらいでした! 参加者の多数決とうえはらの判断によって、今回の技術書の表紙が選ばれました。 ![表紙](/assets/blog/authors/uehara/2025-10-24-techbookfest19-announcement/cover.webp =300x) 新書「TECH BOOK By KINTO Technologies Vol.01」の表紙。 原稿の締切を設ける こちらは重要です。入稿期限は絶対的な締切なので、それに合わせて余裕を持って設定した方が良いです。 また一発で原稿が揃うのはなかなかないと思った方が良いでしょう。 締切は1個だけではなくバックアップのためさらに2、3個設け、それぞれの締切で達成してほしい最低ラインを設定した方が良いでしょう。 執筆者間でレビューしてもらう 執筆者間でレビューしてもらいました。 レビュー項目は社外への外部発信時のレビュー項目に準じつつ、最終的には物理本にすることを意識して項目を設定しておくと良いでしょう。 原稿が出揃わないとレビューすらできないので、締切は早めに設定しておくことをお勧めします(2回目)。 校正 全員分の原稿を取りまとめた上で、全体の紙面の見栄えを調整していきました。 文字サイズが小さいことに気づいたため少し大きくしたり、目次だけで10ページと多くなってしまったので、目次に出す項目を絞ったりしました。 ページ数を4の倍数に調整しておかないと入稿を受け付けてくれないので、うまいことページ数の調整もしていきました。 この影響で表示が崩れてしまった章もあったので、執筆者に修正をお願いしていました。 時間がない中での作業だったので、締め切りは早めに設定しておきましょう(3回目)。 入稿 入稿用のPDFができたら印刷所に入稿していきます。 日光企画さんでは先に入金しておき、入稿用の表紙と本文データをフォームで送信すればOKでした。 ここでデータの不備があれば指摘があり再提出することになりますが、今回は特に不備もなく無事入稿できました。 ブース設営のシミュレーション ブースの設営に詳しい技術広報の方に協力してもらいながら、前日にシミュレーションを行いました。 事前にブースのイメージを膨らませておきつつ必要なものも洗い出せたので、当日ブースを見て何となく思ってたのと違う...みたいなことにはならなかったと思います。 オフライン会場当日の様子 オフライン会場で出来上がった本を初めてみた時、思ったより分厚いなと感じました。 この分厚い本を無料で頒布したので、参加者からは驚きの声も聞かれました。 約300部印刷したのですが全て頒布できたので、初回参加の結果としては上々なのではないかと思っています! https://x.com/KintoTech_Dev/status/1989983294059168095 国会図書館へ納入 技術書典の翌日、国会図書館へ納本しました。日本国内で頒布を目的として発行した出版物は原則納本の対象になるらしく、今回のような技術同人誌でも納本できるようです。 https://www.ndl.go.jp/jp/collect/deposit/deposit.html 日本国内で頒布を目的として発行された出版物は、原則として、すべて納本の対象となります(「納本制度の概要」参照)。 国立国会図書館に出版物を納本いただくと、その書誌データが「全国書誌」として国立国会図書館サーチで検索できるようになります。また、図書館資料として広く利用されるとともに、国民共有の文化的資産として永く保存され、日本国民の知的活動の記録として後世に継承されます。 納本には以下の記事も参考にさせていただきました。 https://qiita.com/mitsuharu_e/items/850ae6688a94ea3d67f3 いざ納本へ 国の中枢ということもあり、銀座線、丸の内線、半蔵門線といった様々な路線が乗り入れています。 2番出口が国会図書館の最寄りのため、路線によっては多少歩くことになりそうです。 駅周辺の案内。出口2が一番最寄りです。私は銀座線で向かったため、歩く量が多かった。 国立国会図書館の案内。納本の方は通用口(西口)へという案内がある。 国立国会図書館の通用口(西口)から入って、建物の入り口にいる警備員さんに納本したい旨伝えると手続きをしてくれます。 国立国会図書館の通用口(西口) 納本の窓口に着いたら、納本する書籍を渡して書類に記入します。手続きが終わったら受領書を渡してくれます。 資料受領書 納本すると以下のように国立国会図書館サーチの結果に表示されるようになり、また国会図書館で資料として読めるようになります。 https://ndlsearch.ndl.go.jp/books/R100000002-I034410569 終わってみて感じたこと 一人で抱え込まず、助けを借りよう 書籍一冊作るだけでも決めることややるべきタスクが多く、一人だけで全て捌き切るのはだいぶ大変です。 他の人でもできる、むしろ他の人の方がうまくやれるタスクはお願いしていった方が良いです。 技術本によって繋がりが増えた 技術書典のオフライン会場での参加者との交流はもちろん、技術本の制作を通して社内有志で繋がりが深まったと思っています。 締切は多重に早めに入れておく 余裕を持って締切を設定したつもりがやってみると意外と時間がないなという気持ちになったので、次はもう少し余裕を持たせていこうと思ってます。多重で締切を設定することは大事です。 まとめ 技術書典19への参加の道のりには大変なことも多かったですが、終わってみると得られるものも多く本当に楽しかったです。 もしこの記事を読んで興味が湧いた方はぜひ技術本制作にチャレンジしてみてください。 また今後の技術書典でパワーアップした姿を見せられるよう頑張っていきます。 https://techbookfest.org/product/qCPrJpWLmKnLt7eWVd9zJ6
アバター
こんにちは!あるいは、まいど! 人事企画G 労務・総務チームのつんつんです。今回は、ついに完成したOsaka Tech Labの新フロアをご紹介します。 正直に言います。以前のオフィスとはもう「全然違う」空間に仕上がりました。 今回のコンセプト KINTOテクノロジーズらしく、車やモビリティを感じさせるデザインに全振りしました。 写真多めで、こだわりのポイントを余すことなくお届けします! エントランス まずエントランスでお出迎えするのは、KINTO Technologiesのロゴ。 これ、ただの看板じゃありません。後ろの骨組みが黒にし、あえてロゴを「白」にして、しかも光らせてます。 そして写真左側この植栽。 「これ本物?」ってよく聞かれますが、一部、フェイクもありますが、殆どリアル(本物)です。 水やり大変そうとか言わないでください。この空間の充実に命かけてるんで。 受付からの廊下の風景 廊下からも緑がいい具合に演出していて一石二鳥 車愛が止まらない 今回のコンセプトの一部でもあり具体化した、「ガレージ」です。 ① こだわりの会議室「ガレージ1・2・3」 会議室エリアは、まさにガレージそのもの。全会議室の床に白いラインを引き駐車場をイメージ、 また核となる3つの会議室には天つりモニターにし、電動ブラインドを設置。 (大事な会議もこれで安心!目線にも気を配りました。) Garage 1: 車の車庫をイメージ 床に白い駐車ラインを引き、有効ボード(ペグボード)を設置。ここに工具とか飾りたい! (予算なくてまだ何もかけてないけど)。 Garage 2: 車の模型をおしゃれに配置。 チップボード風の壁紙にし、無骨なガレージ感を演出。棚を配置しちょっとした小物を置けるようにしました。 Garage 3:西海岸の風を感じる。 ここはちょっとアメリカンな雰囲気。「アメリカンフェンス」というそのまんまな名前の商品を入れて、ヤシの木を導入しました。 廊下もただの廊下じゃない 廊下の天井には道路標識が埋め込まれています。 「大阪ジャンクションまで7m」。これ、適当な数字じゃありません。ちゃんとCADで測って、本当に7mあります。JCTの部屋は後ほど・・・ こういう細かいこだわり、大好きです。 会議室「PIT(ピット)」シリーズ ガレージの次はピットです。ここも部屋ごとに椅子が全部違います。全部変えました。こだわりすぎて記憶飛びそうですが。 写真左からPIT-A, PIT-B, PIT-C PIT-A PIT-B PIT-C 受付と反対側にある4人部屋。受付との一部をガラスにすることにより、ここでも緑が大活躍。癒してくれます。 小会議室の4人部屋。PIT Aと色違いの椅子を入れることで雰囲気がだいぶ変わりました。 机がビンテージ風でかっこいいんですが、真ん中から配線が出せないのが玉に瑕。でも、デザイナーさんの熱意に負けて入れました。 PIT-D ここはプロジェクター投影専用の壁紙を採用。スクリーンを下ろす手間がありません。机は可動式で、スクール形式にも対応。コンセントも爆量で用意してるので、勉強会もやり放題です。 MOTORPOOL 8人用会議部屋。こちらは格式高く設計しており役員会議など行われる設定です。 遊び心満載の「ジャンクション」エリア オフィスの中心にあるのが、みんなが集まる「ジャンクション」。 壁には大阪メンバーのクリエイティブによる巨大アートが! 「この指とまれ」をコンセプトに、大阪らしくたこ焼き、カニ、くいだおれ人形が描かれています。 この指とまれ案のストーリー 技術交流を通じて、部署内や外部の関係者を巻き込みながら、自ら進んで物事を進める力。これが大阪の強み、気質。 この特徴をこの指とまれの指をモチーフに色々な予想外な業種、事、人、技術とコラボしていくことを壁に表現しております。 そしてここには、私がどうしても置きたかった業務用「冷凍庫付き冷蔵庫」があります。 執務エリアからJCTを望む。 コンセプト 情熱のアルゴリズムコード 壁の「この指とまれ」を強調するガラスになっており、 情熱が生まれ広がっていく過程を視覚的にコードで表現してます。 情熱が自分から生まれ → 外部要因と掛け合わさる → 最終的に情熱が 波のように「うねり」になり、社会に大きな影響を与える様子を表現しています。 土禁×リラックスゾーン “PARK” 靴を脱いでくつろげるグリーンカーペットは、まるでオフィス内ピクニック場! 梅田の街並みを眺めながらランチやちょっとしたMTGもOK🍱☕ 謎のこだわり「SUVタイヤ」と「低いホワイトボード」 park部分には、なぜか巨大なタイヤが転がっています。 これ、中身が入った本物のSUVタイヤです。最初はノーマルタイヤだったんですが、「そんなんじゃダメだ!ゴツゴツしたやつがいい!」とわがままを言って変えてもらいました。 こちらは木製モーターベンチ! 足元には本物タイヤを装着!座るだけで車会社らしい遊び心を体感できるミニドライブ気分 2人乗っても大丈夫ですが、めちゃくちゃ重いです。 執務エリアは「広さ」が命 働くデスク周りも妥協してません。 移転前より通路幅、デスク幅を大きくしました。 (デスク幅は1400mm、前後の通路幅は1800mm確保。) 「狭い」とは言わせません。 ストレスフリーで働けます。 まとめ:とにかく「こだわり」がすごい 正直、まだまだ語りきれないこだわりが山ほどあります。 全部書き切れないのですが一部ご紹介 公園のベンチを忠実に再現した木目スラット。 座るだけでほっこりピクニック気分♪ HUBエリア 壁紙はマップ(地図)をイメージ  ファミレス席は移転前の部屋名を踏襲しており歴史も大事にしてます。 サボテンもあります。 オフィスからの風景 大阪駅の真上にあり電車マニアにはたまらない!真ん中より少し右側に線路が見えます。 とにかく、Osaka Tech Labは 「働きたくなるオフィス」 に仕上がりました。 ここまでお付き合い頂きありがとうございました。 現場からは以上です!
アバター
こんにちは、Engineering Officeの守谷(emim)です。 この記事は KINTOテクノロジーズ Advent Calendar 2025 の11日目のものです。 2日目に、 アクセシビリティについての記事 を投稿しましたが、私の主領域はプロダクトデザインです。主軸では、 Engineering Office のメンバーとしてデザイナーの立場でKINTOテクノロジーズ(以下KTC)の状況をとらえ、組織力を上げるための取り組みを行っています。 KTCでは2025年の注力テーマとして、副社長の景山より、2つのテーマ「AIファースト」「リリースファースト」とともに、それを下支えする「ユーザーファースト」と「組織インテンシティ」が掲げられました。 https://blog.kinto-technologies.com/posts/2024-12-25-LookBack2024/ 私はこのうちデザイナーとして、「ユーザーファースト」推進活動に関わっています。私自身は6月入社ですが、このユーザーファーストについての活動は、今年のはじめから様々な方向での組織内の取り組みがおこなわれています。 今回は2025年の総括として、我々の実行した内容をまとめて行きます。 はじめに着手した社内調査から、社内での「ユーザー」の認知にブレがあることがわかった まず初めに行われたのが、ユーザーファースト推進において主軸となる「ユーザー」についての組織内での認知調査です。 ひと口に「ユーザー」と言っても、全員が同じ対象をイメージできているのか?という確認からです。ユーザーが一般的な用語だからゆえの解釈の違いを明確にした後に、具体の推進方法を検討するための調査です。 様々な部署、様々なロールのメンバーを全社から招集し、グループディスカッションを通し現状を把握しました。オンラインホワイトボードツールでの実施や、オンサイトでの付箋でのワークなど、開催形態は様々です。 調査が一通り終わったところで私が参画したこともあり、次に、ビジネスモデルと照らし各階層のユーザーからエンドユーザーまでと製品との関係性を、 Jeff Patton氏の提唱するThe onion model のような形での整理しました。 そこから見えてきたのは、我々の向かう製品とユーザーとの間にかなり距離があることでした。またその距離の遠さからか、事業の複雑さからか、ステークホルダー(チーム内で例えば上下関係のある別職域の人や、取り引きをしている関係会社/グループ会社の担当者、依頼主など)をユーザーと捉えている人も居そうなことがわかりました。 ![ビジネスモデルと照らし各階層のユーザーからエンドユーザーまでとシステムの関係性を表す図と、サービスとユーザーとの距離を表した図](/assets/blog/authors/emim/2025-12-11-userfirst2025/2025-12-11-userfirst2025_img02.png =600x) 「ユーザーのことが見えていない」という課題感からユーザーファーストというテーマが上がってきましたが、開発組織構造も関係していることがわかりました。開発者とユーザーに距離がある場合、どのようにステークホルダーを巻き込み意識付けを行い、ユーザーの要求事項を聞き出すのか、が次のステップになる、と考えました。 これらの情報は、この活動のオーナーである副社長にも共有し、齟齬なく活動を推進していけることの確認にも利用できました。 「UXの成熟度モデル」を参考にした現状分析と推進の次の一手の「共有」 組織課題が明らかになったところで、次に我々が着目したのが 「ユーザー」というワードが開発の現場で出てくるための意識付け 「ユーザー理解」のためのメンバーの巻き込み という2点です。前者については、プロダクト/サービス開発をする以上当たり前のことだと感じるかもしれませんが、全開発者がユーザーに向き合えているかというとまだまだだ不十分という状況です。 このあたりの肌感を探る為、 The 6 Levels of UX Maturity (UXの成熟度モデル)を参考にしながら、現状分析を行いました。このドキュメントには後半に自身で組織の状態を評価(推定)のできるテストが用意されています。 内容及びテストの結果からして、まだ我々は「Stage 2: Limited(限定的)」にいます。現在地が明らかになることで、次レベルの「Stage 3: Emergent(新興)」へ向かうために何をすべきか、という観点の確認が可能となります。 幸いなことに、一般的な組織で次に課題となる「会社側の理解がない(予算がつかない)」という状況にはありません。トップダウンで課題提起がなされている分、動きやすいと感じます。 組織の情報共有の仕組みとしては、他の注力テーマと定期的に情報共有をする機会(定例会議)が設定されています。それにより、例えばリリース分脈でユーザーについての相談をもらえるような関係にあったり、我々からも今後の取り組みとしてAI活用についての相談を上げたりしています。 ただしこれだけでは、限定的な範囲での情報共有となってしまいます。そこで次に、広く全社に向けての情報共有会を行いました。 UXが孤立しないための取り組み 我々の推進活動の共有 まず取り組んだのは、月に一度全社に向けて行われる情報共有会での活動報告です。 事前に各所と合意を得た「現在地」についての改めての報告と、開発者の多い現場でどうしたら「ユーザーの価値」に着目できるのか?という点に絞ってプレゼンを行いました。 「ユーザーの価値」を中心に据えた開発手法として、アジャイル開発と人間中心デザイン(Human Centered Design 、以後HCD)の類似性を引用しながら説明を行いました。 アジャイルの図はよく目にするエンジニアでも、HCDについては初めて耳にしたというリアクションが出ていました。HCDの各プロセスには具体の手法が提供されていることも合わせて提示したことで、この1度の発表だけでエンジニアからでも具体の相談の上がりやすい状況に転換できました。 社内の先行事例の発掘(勉強会の実施) 次に着手したのが、先行事例として取り組まれている方たちをクローズアップし、改めて社内にその活動を共有する勉強会を開催しました。勉強会を通し、「自分達でもできそう」という感覚を持ってもらうことを目的としています。 この勉強会では、具体のHCDプロセスをプロジェクト内で回している事例と、兄弟会社のKINTOの主催のイベントに赴き、ユーザーのサービス利用の状況把握に努めた事例について、改めて発表をしてもらいました。 後者については、過去にこのブログでも本人が記事を起こしてくれています。 https://blog.kinto-technologies.com/posts/2025-08-22-FACTORY_ReportOfRealEvent/ さらに、エンジニアの多い会社であることをふまえ、前述の取り組みをしたフロントエンドのエンジニアや、インタビューを実行したバックエンドのエンジニアを招聘し、パネルディスカッションを通して、具体の心境の変化などを共有してもらいました。 この勉強会以降、具体のユーザーインタビューやカスタマージャーニーマップなど、ユーザーニーズを掘り下げるような具体手法についての相談が上がってくる、という変化が起きました。最近は半月に1件くらいのペースで相談をいただいています。 今後の我々の活動について 現在、この取り組みは私ともう一人、クリエイティブGの大金とで行っています。暫定的に、ユーザーファースト推進活動の社内プロモーションや色々なプロセスの型化/横展開を行う守谷と、プロジェクト内に入ってHCDプロセスを実行していく大金と、という役割分担にしてはいますがもちろん手が足りてはいません。 前述のように、具体のプロセスを実行できる仲間を増やしたり、近い領域の人たちに声を掛け協力を仰いで、この活動を止めない事が最重要ではないかと考えています。また、今回こういった活動を改めて発表をおこなったことで、これまで各領域で埋もれていた活動(=いわゆるサイロ化現象が起きていた)なども、情報として表面化するようになってきました。 冒頭紹介の通り「2025年の」注力テーマのひとつではありますが、ユーザーのことを考えるのはプロダクトデザインの基礎の基礎。せっかくのこの取り組みの火を絶やさないよう、来年以降も研鑽をし続けようと考えています。 最後に。12月23日には KINTOテクノロジーズ Advent Calendar 2025 にて、ともにこの活動を行う大金から彼女自身の挑戦についての記事が公開されます。お楽しみに!
アバター
This article is Day 10 of the KINTO Technologies Advent Calendar 2025 🎅🎄 Introduction I'm Liu (YOU), an Infrastructure Architect in the Cloud Infrastructure Group (CIG) at KINTO Technologies. Starting in November, I've also taken on responsibilities in the Developer Relations Group. In November 2025, our company participated in " CloudNative Days 2025 Winter " as a sponsor. At this event, two of our colleagues gave presentations, the Cloud Infrastructure Group and the Developer Relations Group that I belong to operated the sponsor booth, and many members attended technical sessions, allowing the entire organization to be actively involved. CloudNative Days is the largest event in Japan's cloud-native community. Through this participation, we not only deepened our technical knowledge but also had the opportunity to reconsider the very concept of cloud-native itself. In this article, I'll share the event experience and learnings from the perspectives of speakers, booth operators, and general attendees. Speaker Interviews Koshiro from Cloud Infrastructure Group: " Building the Cloud Foundation for the Future of Mobility Platforms " How I Came to Speak Koshiro : "Having been with the company for less than a year, I didn't know the organization's history or the reasons behind our technology choices. Conveying things I hadn't experienced firsthand was the biggest challenge." Originally, another member was scheduled to speak, but circumstances led to him stepping in at short notice. Having joined in January 2025, this was a challenge for him without fully understanding the organization's history or the background of our technology decisions. Background on Topic Selection He struggled with cloud‑native as a topic. Looking into available resources, he found that many talks went deep into Kubernetes and container technologies. Since our company mainly uses ECS and only uses Kubernetes in limited areas, he couldn’t fully relate. Eventually, he realized that this struggle itself was worth sharing with others who are in the same situation. Koshiro : "Cloud‑native may feel like a high‑level concept and getting there requires organizational trial and error. Technical excellence is important but building a cloud‑native culture within the organization is more important. There is no single best practice." What I most wanted to convey was that cloud-native is a means, not an end . We select appropriate technologies while facing our business needs. By sharing our history of trial and error as it really happened, I hoped to give others facing similar challenges a chance to move forward. Audience Reactions on the Day Venue C, with a capacity of 66 people, was nearly full. Many attendees who had been watching other sessions online moved to this session midway through. Koshiro : "There were people nodding along as they listened, and some took photos of my slides to post on X. At an event where technology seems heavily emphasized, it was striking to see people show interest in my topic." Two people asked questions at the microphone, and three visited "Ask the Speaker," giving me a strong sense of accomplishment for my first presentation. What stayed with me most were the questions about Terraform structure and the platform organization's decision-making. These are questions without definitive answers and I was reminded that everyone struggles with these things . Lessons Learned This was a substantial 40-minute presentation, and through the intensive preparation and practice, Koshiro-san realized how important regular output truly is. Koshiro : "I realized that if I had been giving a presentation regularly, creating slides would have been much easier. If I'd been writing tech blogs, I could have adapted and utilized that content. I've resolved to work harder on my output." He also gained an important insight about using AI. From his initial failed attempt to create a talk script with AI, he learned this lesson: "First establish what message you want to convey, then ask AI how to express it. Never start by asking AI. It's a recipe for failure." Looking Ahead For next time, he wants to build experience with shorter 20-minute presentations and also challenge himself to present more technology-focused topics. Additionally, since it's a community event, he expressed a desire to deepen connections with participants through group work and mutual learning. Koshiro : "Next year, we'll be hosting a cloud-native conference. KINTO Technologies is one of the most advanced organizations within the Toyota Group when it comes to cloud adoption. While we don't have everything figured out yet, we've reached a stage where we can build high-level solutions together. I'd be happy to see more opportunities for collaboration and study sessions." Lee-san from Platform G: " A New Era of Alert Response Pioneered by AI Agents " The Decision to Submit Proposal to CFP Lee-san submitted a proposal to CFP with a "let's just try" attitude, thinking the chances of acceptance were low. The reasons for applying were clear: to enhance the company's external communication power, contribute to engineer recruitment within the group, and gain opportunities for external communication. Since the submission was completely separate from sponsor work, being accepted came as a surprise. Struggles with Material Preparation He struggled most with determining the right skill level for the audience when creating the materials. Lee : "With both experts and beginners in the audience, I struggled to decide what level to aim for. I wanted the content to be easy to understand even for those unfamiliar with the topic, but if I made it too light, it wouldn’t satisfy people looking for deeper knowledge. Striking that balance was the hardest part." I particularly focused on storytelling. I was conscious of the flow: "there was this challenge → we responded this way → the result was this → now we're at this point," and reviewed the overall structure multiple times to make the narrative flow naturally. Material preparation took over a week, continuously improving the script and presentation. Unexpected Events On the day, his biggest concern was whether PowerPoint's script feature would be visible on the audience's screen. For a 40-minute presentation, going without notes would be tough, so during lunch he went to the venue myself to ask for a quick run‑through. Lee : "They provided basic information for speakers, such as arrival times and HDMI connection details, but there should have been more guidance on things like rehearsals and checking script display. For our members who present at future events, I’d recommend visiting the venue in advance to confirm these details themselves." Audience Response The presentation was well-received overall, with fellow speaker Koshiro-san commenting that it was "the most impressive session of the day." After the presentation, 4-5 people came with questions, and Lee-san said he could feel the high interest in AI Agents. What stood out to him most was a question about context engineering: How do you distinguish between information needed for AI and information needed only by humans but not by AI? He shared the countermeasures currently under consideration for this question as well. Lee : "There's the issue of LLM model input limitations, and if the context gets too large, it exceeds the limits. We plan to address this with a single-agent configuration, verifying a method to summarize and compress history using LangChain's new features. I want to talk about these improvements at next year's conferences or in technical articles." Lessons Learned For him, this was the first time speaking at a large-scale open community event, so there was a lot to gain. Lee : "I was nervous, but I found that there was no need to be as scared as I was. If there are people who want to try, I recommend giving it a shot. I was greatly inspired from other speakers and participants, and my technical motivation increased." In terms of technical takeaways, he found the presentations on incident‑management SaaS and CyberAgent’s Ōyama-san’s talk on Loki and Prometheus particularly insightful. He also gained a few dozen new followers on X, which helped broaden his connections within the community. Looking Ahead There are areas for improvement regarding time management. When he practiced his presentation, he noticed that he finished earlier than expected. However, during the actual presentation, he got nervous and couldn’t get the words out. He ended up running short on time and had to skip a page. Next time, he is thinking of giving a talk on improving context engineering and methods for evaluating agents. Lee : "It’s difficult to evaluate the effectiveness of an agent because creating proper evaluation criteria is challenging. Even when alerts seem to be the same, the underlying causes can differ, which makes it hard to build accurate ground‑truth data. This is widely recognized as a common issue across the industry. Context engineering is a field that has recently been gaining attention, but best practices have yet to be established. I believe this is a topic many people are eager to learn about." His advice is clear. Lee : "What matters most is simply applying. Once you get accepted, your future self will figure it out. It boosts your motivation and inspiration, and it also helps raise your company’s presence, benefiting both sides." Other Speakers Tsuji-san, the Manager of Cloud Infrastructure Group, also gave a talk about the Toyota Group's technical community! Tsuji-san from Cloud Infrastructure Group: " Introducing TURTLE, the Toyota Group Engineer Community! " Booth Operation Interview Many people from our company helped with this booth operation. With cooperation from various groups including Cloud Infrastructure Group, Platform Group, HR Group, and Developer Relations Group, we were able to run an exciting booth operation and complete it successfully. For the booth operation interview, we proceeded in a discussion format, with participation from: Cloud Infrastructure Group (CIG): Koshiro, Shirai, Yasuda, Matsuo Developer Relations Group: Murayama Challenges in the Preparation Stage This was CIG's first time operating a booth, so we didn't know what to do, but our company has the Developer Relations Group that fully supports events like this from planning to reflection, enhancing both internal and external communication power. This time too, Murayama-san from the Developer Relations Group provided support from the booth preparation stage. Her collaboration with Manager Tsuji-san was a key factor in successfully running the booth. Murayama : "I consulted with Manager Tsuji-san in advance and briefly communicated what information we wanted. Things progressed through CIG's regular meetings, and before I knew it, the shift schedule was ready, which was very helpful." Murayama : "Also, at a conference we sponsored over the weekend before the event, we displayed a board featuring our system architecture diagram, which was very well received and helped us have many conversations with attendees. So, even though it was the day before the event, I suggested, "Do you have any architecture diagrams we could display?" and they immediately provided the perfect architecture diagram. We barely made it in time for printing, but we managed to have it at our booth. Since it served as a great conversation starter, I'm really glad we were able to prepare it!" I felt the advice was truly valuable, because understanding what attracts people to a booth is something you can’t really grasp without having run many booths yourself. For Cloud Infrastructure Group, many people participated in booth operation for the first time, so most of it was about preparing ourselves to get into the right mindset. Shirai : "I made a conscious effort to stay cheerful and energetic so as not to give off any negative impression. I wanted visitors to have a good impression of our booth." As Shirai-san said, we put a lot of effort into presenting a positive attitude to the people who visited our booth. Observations and Insights on the Day Cloud Infrastructure Group had two main points they wanted to convey at CloudNative Days 2025 Winter. According to Koshiro-san, who spoke at the sponsor session: Koshiro : "First, we wanted people to know what the Cloud Infrastructure Group[^1] does. Second, it was about publicizing the activities of the Solutions Team within our group. Our company does various activities not only for our own services but for the entire Toyota Group. We wanted visitors to know a little about it." Specifically, we communicated with visitors through the question board prepared at the booth: Q3. Do you know KINTO Technologies? Through this question, we were able to broadly communicate our activities as Cloud Infrastructure Group and the company's direction while engaging in conversation. There were also other important insights: Koshiro : "Not all event participants come to the booth, but those who do come and listen are at least interested in KINTO Technologies. Also, I noticed that a certain number of people aren't involved in cloud-native itself. This gap between the event title and the actual attendees stood out to me." Initially, we had the impression that many cloud-native engineers would participate since it was a cloud-native event, but we found that there were more people who want to pursue cloud-native in the future than engineers already doing cloud-native work. In fact, the venue had people from various backgrounds, including app developers, sales representatives, designers, reporters, and students. Yasuda : "I wanted more people to know about KINTO Technologies, but half of the visitors already knew about our company. It was amazing that many people knew about KINTO Technologies from recent events like the Developer Productivity Conference and Gijutsushoten ." We also found that CloudNativeDays participants include very few infrastructure specialists. Many people said that app developers often also handle infrastructure responsibilities, and it's very challenging to write application code while also creating infrastructure resources and handling security. KINTO Technologies has dedicated infrastructure and SRE/security teams like ours, with clearly divided roles, so I strongly feel once again that we can focus on our respective areas of expertise. There were also opinions that it was good to hear various people's perspectives through the question board. We adjusted our questions depending on visitors' backgrounds, personalities, and points of interest. Q1. What's your cloud style? → Work style & Development style Q2. What is "good infrastructure" to you? → "Good work" or "Good system" Matsuo : "I'm glad we could go beyond the questions on the board and hear about people's perspectives and what they prioritize. What left an impression was that surprisingly many people work with cloud services other than AWS. Since I've only used AWS, it made me want to try multi-cloud." Many people with various roles and a strong interest in technology participated: Shirai : "We had visitors with diverse roles, and it was difficult to have deeper conversations with people in roles outside my area of expertise. Going forward, I want to build my foundation (knowledge and communication skills) so I can talk with people in a wide range of roles." Even for CloudNative booth operations, I feel we need to prepare a system that can handle visitors of all roles, not just infrastructure specialist positions. Results and Improvements As one of the biggest achievements this time, we had this conversation: Koshiro : "Almost everyone in the Infrastructure Team and Kaizen Team participated in the event as our group's operators. It's quite a rare experience. I'm grateful we could share the same event experience. While running the event, we were able to share learnings with each other like 'this is how it works' or 'that's how it should be.'" Shirai : "By holding an event with the whole team, the team's sense of unity increased even more. Also, by hearing about what other companies are practicing, we could gain new insights." Participating individually in events is good, but when participating as a team, you can establish the same mindset with each other. I believe we were able to foster team growth and raise awareness, and by working together, we could once again establish a path for continuous technical outreach. On the other hand, there were also areas for improvement. This time, we also had the purpose of listening to sessions we were interested in as participants to broaden our knowledge, so we created a shift schedule within the group in advance. Matsuo : "We created a shift schedule, but when it was my turn and I headed to the booth, I was surprised that everyone was at the booth." Koshiro : "Next time we run a booth, it would be better to have 3 people at the booth while others attend sessions for input. We need to think about ways to be more efficient, like finding blog topics at sessions." What we recognized as an issue wasn't the shift problem but time efficiency. Since CIG participated as both booth operators and attendees, I think it was difficult to balance between running the booth and gaining our own takeaways by attending sessions. Still, by reflecting properly, we managed to bring clarity to the challenges each of us had in mind. Many visitors indicated on the question board that they already knew KINTO Technologies which is a clear result of our ongoing promotional efforts and proof that events aren't just one-time things. The Cloud Infrastructure Group will keep contributing to KINTO Technologies' technical outreach, and this experience will be a key ingredient for our future endeavors. The Essence of Cloud-native Finally, we explored in detail the results of the question board filled out by everyone. Shirai : "For the question 'What is good infrastructure?', many people answered, 'Something you can be passionate about.' Exploring it further, understanding your own service to the point where you can love it might be one way to deepen the understanding of the essence of cloud native." Koshiro-san re-emphasized the message he delivered in his talk: Koshiro : "No organization has cloud-native perfectly figured out from the start. There are various gradations. It's necessary to build not just the technology, but also the culture and organization. Talking with various companies at the sponsor booths, I felt this anew." Attendee Interview Shimamura from Platform Group Shimamura-san planned to attend to see Lee's presentation from the same group, and also he had submitted a CFP himself. Additionally, his team had recently started working with Kubernetes and EKS, so the team's technical interest was growing. Sessions that stood out: Nintendo's Case Study Shimamura-san was amzaed by Nintendo's session. Shimamura : "It was an on-premise case study and I wanted to incorporate this culture. They run full regression tests once a day, and I was surprised they use test results for purposes other than bug reporting." What was particularly interesting was the multi-purpose use of test results. Shimamura : "From a UI perspective, designers don't just look at surface-level design alone but use it as decision-making material like 'considering the build result images, this would (match the surrounding atmosphere) better.' There's a flow of events from the past, and you can tell if it's natural within that flow. They also use it for voice actor dubbing. Using automated testing not just for finding bugs, but for other purposes too. It's a culture of proactively finding and improving points across multiple fronts using test results." And he also reflected on the differences between Nintendo's case and our company: Shimamura : "I felt that KINTO still lacks this culture. This mindset of making things possible, noticing issues, and fixing them doesn't come naturally to us yet. I felt it's necessary not only for improving websites that have become heavy, but also for delivering KINTO's services better." Technical Gains Shimamura-san shared several technical gains: Realizing the cause of Alloy log double-forwarding : When we recently did blue-green deployment, I noticed something similar to what I thought was the cause of Alloy logs being double-forwarded and used it as input for investigation. Docker build security : I learned there are frameworks for signatures and verification to check if built container images are contaminated with malware. Given recent circumstances, I shared this information with the security team. Reconfirming RCA's advantages : While incident management SaaS solutions only apply AI to incident management processes, our company has built an end-to-end solution with RCA. This reaffirmed the advantages and benefits of RCA: that everything must be unified and handled end-to-end. Conversely, he also shared some questions he felt about certain sessions: In some cases, cloud-native technology was becoming the purpose rather than a means to solve problems I wanted to hear case-specific stories rather than general knowledge Looking Ahead Shimamura-san shared improvement points and outlook for next time: Shimamura : "I want to register for event sessions early so I can make sure I attend the ones I’m interested in. Also, instead of keeping the information I gain to myself, I want to make it accessible to other team members. If people don’t know what happened or what examples I found, then the time I spent participating would feel wasted. I believe knowledge doesn’t mean much unless it’s shared and spread." He also showed interest in giving a talk: Shimamura : "My CFP submission was rejected twice, so I definitely want to give a talk next time. However, topics are the problem. I've already talked about RCA, and culture theory at Platform Engineering Meetup . I need to find new topics." Conclusion Through our participation in CloudNative Days 2025 Winter, we gained many learnings and insights. Technology and Culture as Two Wheels Speakers Koshiro-san and Lee-san each talked about cloud-native from different approaches. Koshiro-san from the perspective of organizational culture, Lee-san from the perspective of technical practice. However, what both had in common was the message that cloud-native is a means, not an end . Through booth operation, we reconfirmed that "no organization deploys cloud-native perfectly from the start." We need to build not just technology, but culture and organization as well. As attendee Shimamura learned from Nintendo's case, cloud-native is a culture, not a technology. A culture of proactively finding and improving points, the idea of using test results for multiple purposes, and above all, understanding your own service to the point where you can "love" it. These might be the essence of cloud native. Team Unity One of the biggest achievements of attending this event was that the Infrastructure Team and Kaizen Team—almost everyone—could participate as group operators. Being able to share the same event experience and learnings together greatly increased the team's sense of unity. People from other groups also helped us, and we were able to have a very good time. The Importance of Continuous Output What both speakers commonly felt was the importance of daily output. Having experienced the difficulty of preparing a 40-minute presentation, they realized the need for a habit of continuous output through tech blogs and study session presentations. Also, as Shimamura-san pointed out, it's important not to keep information gained at events to yourself, but to share it with the team and organization. We believe that spreading knowledge leads to organizational growth as a whole. Looking Ahead In 2026, the cloud-native conference will be held (a joint event of Platform Engineering Meetup, SRE Conference, and CloudNativeDays). We also plan to exhibit at JAWS Days. To everyone struggling with cloud-native, here's a message from Koshiro-san: "Don't be driven by technology. Let's work together on culture-building and organization-building, striving together for business success and achieving the mission beyond business. Cloud-native is a means, not an end." And don't forget Lee-san's advice: "It's important to submit an application first. Don't be afraid. Just submit papers. Once you're accepted, your future self will figure it out somehow." We hope to apply the learnings and insights gained through this event participation to future organization-building, culture-building, and technical challenges. We look forward to growing together through continuous interaction with the community. [^1]: At the time of writing this article, Cloud Infrastructure Group consists of three teams: the Infrastructure Team, Kaizen Team, and Solutions Team.
アバター
Note: This article was translated from Japanese with the assistance of AI. This article is the Day 10 entry of the KINTO Technologies Advent Calendar 2025 🎅🎄 Table of Contents Introduction Summary What We Built Features Prerequisites Operational Best Practices Architecture Implementation Details Usage Results Conclusion Introduction Hi, I'm Miyashita, an engineer in the Common Services Development Group at KINTO Technologies. Modern development environments using AWS and similar platforms are easy to scale up and down, but this flexibility often leads to environment sprawl. Our development setup has grown to include dev, dev2, stg, stg2...stg5, ins, prod, and more. As a result, the values managed in AWS Systems Manager Parameter Store have multiplied (number of environments × number of parameters), leading to frequent mistakes. Here are some examples of errors we encountered: Updated Parameter Store for dev but forgot to update stg KEY and VALUE were correct, but forgot to add tags Deployment failed because we forgot to register a newly required parameter When manually copying from dev to stg, accidentally copied values that should have been environment-specific Additionally, it was difficult to know the current state of Parameter Store values. Checking each environment through the browser was tedious and often got postponed, creating a vicious cycle of more careless mistakes. To solve this, we built an automation system based on the principle of "consolidating all environment parameters in a local YAML file and syncing that file with AWS." This article introduces that approach. :::message For simplicity, this article uses 3 representative environments: dev / stg / prod . In practice, we have many more environments including dev2, stg2-stg5, inte, ins, prod, etc. ::: Summary Automated AWS Parameter Store management for 10+ environments × 50+ parameters because manual management was too cumbersome Visualize all parameters in YAML + one-click sync with GitHub Actions Eliminated update oversights, careless mistakes, and tedious manual copy-paste work What We Built We built this system combining these three components: YAML file - Centralized management of Parameter Store values for all environments Python scripts - Sync processing between YAML and AWS GitHub Actions - One-click deployment to all environments Features 1. YAML Visualization Previously, checking Parameter Store values for each environment required logging into the AWS console multiple times, switching accounts for each environment. Now, opening a single YAML file in the repository shows all parameters for all environments at a glance. parameters: # Parameters with different values per environment - key: api/endpoint description: "API endpoint" environment_values: dev: "https://dev-api.example.com" stg: "https://stg-api.example.com" prod: "https://prod-api.example.com" # Common value across all environments - key: app/timeout description: "Timeout setting (seconds)" default_value: "30" # Sensitive information (SecureString) # Value retrieved from GitHub Secrets via environment variables (e.g., SSM__DB__PASSWORD) - key: db/password description: "Database password" type: "SecureString" Benefits: See at a glance which value is used in which environment Git-managed, so change history is trackable PR reviews can catch value errors before deployment 2. One-Click Sync Simply manually trigger the GitHub Actions workflow, and all environments' Parameter Stores automatically sync with the YAML contents. Using matrix strategy , multiple environments (dev, stg, prod) run in parallel, so execution time stays nearly constant even as environments increase. No more clicking around the AWS console switching between environments. Prerequisites This system works under the following conditions: AWS CLI available on GitHub Actions runners Permissions for Parameter Store: ssm:GetParametersByPath , ssm:PutParameter , ssm:AddTagsToResource GitHub Actions can access AWS (via Access Key or OIDC ) Python 3.11 + pyyaml available :::message This article uses IAM user access keys for simplicity. For production use, we recommend OIDC (OpenID Connect) for keyless authentication to improve security. ::: Operational Best Practices While automation brings convenience, we must also consider the risk of mistakes. Our team implements these practices: Restrict SecureString Operation Permissions We limit who can edit GitHub Secrets, minimizing the number of people who can handle sensitive information. Separate Workflow with Approval for Production When updating Parameter Store for production, we use a dedicated production workflow that includes a Slack approval step. The approval request displays the list of parameters to be updated, so reviewers can confirm "what will change" before approving. This prevents accidental production updates. Architecture graph TB YAML["aws-params.yml"] Secrets["GitHub Secrets (per env)"] subgraph GHA["GitHub Actions"] Workflow["3 environments in parallel"] end subgraph Scripts["Python"] Update["update_aws_params.py"] end subgraph AWS["AWS Parameter Store"] dev["/dev/app/config/"] stg["/stg/app/config/"] prod["/prod/app/config/"] end YAML --> Workflow Secrets --> Workflow Workflow --> Update Update --> dev Update --> stg Update --> prod style YAML fill:#e1f5ff style Secrets fill:#fff0f0 style GHA fill:#fff4e1 style Scripts fill:#f0ffe1 style AWS fill:#ffe1e1 Implementation Details Directory Structure $ tree .github .github ├── aws-params.yml ├── scripts │ ├── aws_param_common.py │ └── update_aws_params.py └── workflows └── sync-parameters.yml YAML Design All environment parameters are consolidated in .github/aws-params.yml (see the "Features" section for YAML examples). Handling SecureString Hardcoding sensitive information like DB passwords in YAML is a security risk. Instead, we separated responsibilities: "YAML contains only key definitions" while "actual values are in GitHub Secrets" . The Python script checks the YAML definition, and if type: SecureString , it reads from the corresponding environment variable. Naming Convention: YAML key: db/password → Environment variable: SSM__DB__PASSWORD Separating SecureString Values by Environment DB passwords often differ by environment. Using GitHub Actions Environments feature, you can set different Secrets for each environment. Setup Steps: Create environments in GitHub repository Settings → Environments ( dev , stg , prod ) Register SSM__DB__PASSWORD etc. in each environment's Secrets (different values per environment) Specify environment: ${{ matrix.env }} in the workflow This way, the dev environment automatically uses dev's DB password, and stg uses stg's. Note: Parameter Store vs Secrets Manager You might think "Shouldn't sensitive information go in Secrets Manager ?" Here's a comparison: Parameter Store (SecureString) Secrets Manager Cost Standard parameters are free $0.40/secret/month Rotation Manual Automatic rotation available Best for API keys with low update frequency When automatic DB password rotation is needed In most cases, Parameter Store (SecureString) is sufficient. Consider Secrets Manager when you need "automatic RDS password rotation." :::message Note: You can manage Secrets Manager values in a similar way. cmd = [ "aws", "secretsmanager", "put-secret-value", "--secret-id", secret_id, "--secret-string", value ] subprocess.run(cmd, check=True) ::: Python Script Structure aws_param_common.py - Common Functions #!/usr/bin/env python3 """AWS Parameter Store Common Functions""" import os import sys import json import subprocess from typing import Dict, Any, Tuple import yaml def get_env_name() -> str: """Get environment name""" env = os.environ.get("ENV_NAME") if not env: print("Error: ENV_NAME environment variable is not set") sys.exit(1) return env def get_prefix(env: str) -> str: """Return prefix based on environment""" return f"/{env}/app/config/" def load_yaml_config() -> Tuple[Dict[str, Any], set]: """Load YAML file""" yaml_path = os.path.join(os.path.dirname(__file__), "..", "aws-params.yml") with open(yaml_path, "r", encoding="utf-8") as f: config = yaml.safe_load(f) yaml_keys = {param["key"] for param in config.get("parameters", [])} return config, yaml_keys def get_existing_params(env: str) -> Dict[str, Dict[str, Any]]: """Get existing parameters from AWS SSM (with pagination support)""" prefix = get_prefix(env) existing_params = {} next_token = None while True: cmd = [ "aws", "ssm", "get-parameters-by-path", "--path", prefix, "--recursive", "--with-decryption", "--output", "json" ] if next_token: cmd.extend(["--next-token", next_token]) try: result = subprocess.run(cmd, check=True, capture_output=True, text=True) data = json.loads(result.stdout) params_data = data.get("Parameters", []) except subprocess.CalledProcessError as e: print(f"Warning: Failed to get parameters: {e.stderr}") return {} for param in params_data: key = param["Name"].replace(prefix, "") existing_params[key] = { "value": param["Value"], "type": param["Type"], "version": param.get("Version", 1) } next_token = data.get("NextToken") if not next_token: break return existing_params def get_param_value(param: Dict[str, Any], env: str) -> str | None: """Get parameter value (SecureString from env vars, others from YAML)""" # For SecureString, get from environment variable if param.get("type") == "SecureString": env_var_name = "SSM__" + param["key"].upper().replace("/", "__") value = os.environ.get(env_var_name) if not value: print(f"Warning: Environment variable {env_var_name} for SecureString {param['key']} is not set") return None return value # Environment-specific value env_values = param.get("environment_values", {}) if env in env_values: return str(env_values[env]) # Default value if "default_value" in param: return str(param["default_value"]) return None def validate_param(param: Dict[str, Any], env: str) -> Tuple[bool, str, Dict[str, Any] | None]: """Validate parameter""" key = param.get("key") if not key: return False, "key is not defined", None value = get_param_value(param, env) if value is None: return False, f"{key}: value for environment {env} is not defined", None param_info = { "key": key, "value": value, "type": param.get("type", "String"), "description": param.get("description", "") } return True, "", param_info def update_parameter(param_info: Dict[str, Any], env: str) -> bool: """Update parameter""" prefix = get_prefix(env) full_name = prefix + param_info["key"] cmd = [ "aws", "ssm", "put-parameter", "--name", full_name, "--value", param_info["value"], "--type", param_info["type"], "--overwrite" ] if param_info.get("description"): cmd.extend(["--description", param_info["description"]]) try: subprocess.run(cmd, check=True, capture_output=True, text=True) add_tags(full_name, env) # Add tags return True except subprocess.CalledProcessError as e: print(f"Error: Failed to update {param_info['key']}: {e.stderr}") return False def add_tags(parameter_name: str, env: str) -> bool: """Add tags to parameter""" cmd = [ "aws", "ssm", "add-tags-to-resource", "--resource-type", "Parameter", "--resource-id", parameter_name, "--tags", f"Key=Environment,Value={env}", "Key=SID,Value=backend-api" ] try: subprocess.run(cmd, check=True, capture_output=True, text=True) return True except subprocess.CalledProcessError as e: print(f"Warning: Failed to add tags: {e.stderr}") return False Key Points: get_existing_params : Pagination support for 50+ parameters get_param_value : SecureString from environment variables, regular parameters from YAML update_parameter : Calls add_tags after parameter update to apply tags About Tags Tags are automatically applied when creating parameters. Tags are useful for searching in the AWS console and cost management, and some systems require tags to read parameters. Tag Value Description Environment dev , stg , prod Environment name automatically set at runtime SID backend-api Service identifier (replace with your service name) update_aws_params.py - Update Script #!/usr/bin/env python3 """AWS Parameter Store Update Script""" import sys import aws_param_common as common def update_parameters(): """Update parameters and report results""" env = common.get_env_name() print(f"=== Environment: {env} ===") print(f"Prefix: {common.get_prefix(env)}") print() config, yaml_keys = common.load_yaml_config() existing_params = common.get_existing_params(env) print(f"Existing parameters: {len(existing_params)}") print() updated_params = [] skipped_params = [] failed_params = [] for param in config.get("parameters", []): is_valid, error_msg, param_info = common.validate_param(param, env) if not is_valid: print(f"[Skip] {error_msg}") continue param_key = param_info["key"] value = param_info["value"] # Compare with existing value if param_key in existing_params: if existing_params[param_key]["value"] == value: print(f"[Skip] {param_key}: no change") skipped_params.append(param_key) continue print(f"[Update] {param_key}: updating value") else: print(f"[New] {param_key}: adding new parameter") # Update parameter success = common.update_parameter(param_info, env) if success: updated_params.append(param_key) print(f" ✓ Done") else: failed_params.append(param_key) print(f" ✗ Failed") # Result summary print() print("=== Summary ===") print(f"Updated: {len(updated_params)}") print(f"Skipped (no change): {len(skipped_params)}") print(f"Failed: {len(failed_params)}") if failed_params: print() print("Failed parameters:") for key in failed_params: print(f" - {key}") sys.exit(1) print() print("✓ Completed successfully") if __name__ == "__main__": update_parameters() Key Points: Skip parameters with unchanged values (prevent unnecessary updates) Output statistics as summary Exit with code 1 on failure GitHub Actions Workflow name: Sync AWS Parameter Store on: workflow_dispatch: # Manual trigger jobs: sync-parameters: runs-on: ubuntu-latest strategy: matrix: env: [dev, stg, prod] environment: ${{ matrix.env }} # Use environment-specific Secrets steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: pip install pyyaml - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 - name: Sync Parameters env: ENV_NAME: ${{ matrix.env }} # For SecureString (from environment-specific GitHub Secrets) SSM__DB__PASSWORD: ${{ secrets.SSM__DB__PASSWORD }} SSM__API__SECRET_KEY: ${{ secrets.SSM__API__SECRET_KEY }} run: | cd .github/scripts python update_aws_params.py Key Points: strategy.matrix runs multiple environments in parallel environment: ${{ matrix.env }} uses environment-specific Secrets (different DB passwords for dev and prod) SecureString values passed to script via environment variables Parameters with unchanged values are automatically skipped Usage 1. Adding/Changing Parameters Just edit .github/aws-params.yml and submit a PR. parameters: # Add new parameter - key: feature/enable_payment_v2 description: "Enable new payment system" environment_values: dev: "true" stg: "false" prod: "false" 2. Deploying to All Environments Open GitHub Actions page Select Sync AWS Parameter Store Click Run workflow button Deploys to all environments in parallel 3. Adding SecureString Parameters Add definition to YAML: - key: payment/api_key description: "Payment API key" type: "SecureString" Register value in GitHub Environments: Settings → Environments → Register in each environment's Secrets (dev, stg, prod) Secret name: SSM__PAYMENT__API_KEY Value: (actual value, different per environment) Add to workflow file's environment variables section: env: SSM__PAYMENT__API_KEY: ${{ secrets.SSM__PAYMENT__API_KEY }} 4. Demo Select and run the action we created from the GitHub Actions page. Confirm the action completed successfully. You can see each environment ran in parallel. Open the AWS Parameter Store console to verify. The parameters are registered. Success! Results Concrete Results Work time : Environments × 5 min → One click (50 min saved for 10 environments) Update oversights : Several per month → Zero Verification : Open AWS console → Just look at YAML Conclusion The "too many environments" problem in the cloud era is a challenge many teams face. The key points of this approach: YAML visualization - Manage all environment parameters in one file One-click sync - Auto-deploy with GitHub Actions SecureString support - Securely manage sensitive information No special technology required—just GitHub Actions + Python + AWS CLI . I hope this helps those struggling with Parameter Store management or dealing with environment sprawl. Thank you for reading!
アバター
この記事は KINTOテクノロジーズアドベントカレンダー2025 の10日目の記事です🎅🎄 目次 はじめに この記事の要約 作ったもの 特徴 前提条件 運用上の工夫 アーキテクチャ 実装のポイント 使い方 導入効果 まとめ はじめに こんにちは。KINTOテクノロジーズの共通サービス開発グループのエンジニア、宮下です。 AWSなどを使う現在の開発環境は、簡単に増やしたり減らしたりできる反面、環境の数が増えていきがちです。 我々の開発環境も、dev、dev2、stg、stg2...stg5、ins、prodのように、とても多くなってしまっています。 そのため、 AWS Systems Manager Parameter Store で管理する値も、環境数 × パラメータ数の組み合わせでどんどん増えていき、ケアレスミスが多発していました。 例えば以下のようなミスです。 devのParameter Storeは最新化したけれど、stgのパラメータを更新し忘れていた KEY, VALUEは正しいけれど、タグを入れ忘れていた デプロイに失敗するので調査したら、新規追加のパラメータを登録し忘れていたのが原因だった devからstgへ手作業でコピペする際、環境ごとに変えるべき値をそのままコピペしてしまった また、現在のParameter Storeの値がどうなっているかも分かりづらく、ブラウザでいちいち各環境に入って確認するのが面倒で、つい後回しになりがちでした。その結果、よりケアレスミスが起きる悪循環に陥っていました。 そこで、「ローカルのYAMLファイルに各環境のパラメータを集約し、そのファイルとAWSを同期する」という方針で自動化の仕組みを作りました。今回はそのアイデアを紹介します。 :::message 本記事では説明を簡潔にするために、代表として dev / stg / prod の3環境を例に説明します。 実際の現場ではこの他に dev2, stg2〜stg5, inte, ins, prodなど、さらに多くの環境があります。 ::: この記事の要約 10環境以上 × 50以上のパラメータ のAWS Parameter Store管理が煩雑すぎたので自動化した YAMLで全パラメータを可視化 + GitHub Actionsでワンクリック同期 更新漏れやケアレスミスをゼロにし、面倒くさい機械的なコピペ作業を無くした 作ったもの 以下の3つを組み合わせてこの仕組みを構築しました: YAMLファイル - 全環境のParameter Storeの値を一元管理 Pythonスクリプト - YAMLとAWS間の同期処理 GitHub Actions - ワンクリックで全環境に反映 特徴 1. YAML可視化 従来は各環境のParameter Storeの値を確認するために、AWSコンソールに各環境ごとにアカウントを切り替えて何度もログインする必要がありました。 今は、リポジトリにある1つのYAMLファイルを開けば、全環境の全パラメータが一覧できます。 parameters: # 環境ごとに値が異なるパラメータ - key: api/endpoint description: "APIエンドポイント" environment_values: dev: "https://dev-api.example.com" stg: "https://stg-api.example.com" prod: "https://prod-api.example.com" # 全環境で共通の値 - key: app/timeout description: "タイムアウト設定(秒)" default_value: "30" # 機密情報(SecureString) # 値はGitHub Secretsから環境変数経由で取得(例: SSM__DB__PASSWORD) - key: db/password description: "データベースパスワード" type: "SecureString" メリット: どの環境でどの値を使っているか一目瞭然 Git管理できるので変更履歴も追える PRレビューで値の間違いを事前に防げる 2. ワンクリック同期 GitHub Actions のワークフローを手動実行するだけで、全環境のParameter Storeが自動的にYAMLの内容と同期されます。 マトリクス・ストラテジー により、複数環境(dev, stg, prod)が並列で実行されるため、環境が増えても実行時間はほぼ変わりません。 AWSコンソールで環境を切り替えながらポチポチする必要がなくなりました。 前提条件 この仕組みは以下の前提で動作します。 AWS CLI がGitHub Actionsランナー上で利用できること Parameter Storeへの ssm:GetParametersByPath , ssm:PutParameter , ssm:AddTagsToResource 権限があること GitHub ActionsからAWSにアクセスできること(Access Keyもしくは OIDC など) Python 3.11 + pyyaml が利用できること :::message 本記事では簡略化のためにIAMユーザーのアクセスキーを使用していますが、本番運用ではセキュリティ向上のため OIDC (OpenID Connect) を使用したキーレス認証を推奨します。 ::: 運用上の工夫 自動化で便利になる一方、誤操作のリスクも考慮が必要です。我々のチームでは以下の工夫をしています。 SecureStringの操作権限を絞る GitHub Secretsを編集できる人を限定し、機密情報を扱える人を最小限にしています。 本番環境への反映はワークフローを分けて承認制に 本番環境のParameter Store更新時は、本番環境専用のワークフローを使い、ワークフローの中でSlackで承認ステップを挟む運用にしています。承認依頼時には更新対象のパラメータ一覧がSlackに表示されるため、「何が変わるのか」を確認してから承認できます。これにより、うっかり本番を更新してしまう事故を防いでいます。 アーキテクチャ graph TB YAML["aws-params.yml"] Secrets["GitHub Secrets (per env)"] subgraph GHA["GitHub Actions"] Workflow["3環境並列実行"] end subgraph Scripts["Python"] Update["update_aws_params.py"] end subgraph AWS["AWS Parameter Store"] dev["/dev/app/config/"] stg["/stg/app/config/"] prod["/prod/app/config/"] end YAML --> Workflow Secrets --> Workflow Workflow --> Update Update --> dev Update --> stg Update --> prod style YAML fill:#e1f5ff style Secrets fill:#fff0f0 style GHA fill:#fff4e1 style Scripts fill:#f0ffe1 style AWS fill:#ffe1e1 実装のポイント ディレクトリ構成 $ tree .github .github ├── aws-params.yml ├── scripts │ ├── aws_param_common.py │ └── update_aws_params.py └── workflows └── sync-parameters.yml YAML設計 全環境のパラメータを .github/aws-params.yml に集約しています(YAMLの例は「特徴」セクションを参照)。 SecureStringの扱い DBのパスワードなどの機密情報をYAMLにベタ書きするのはセキュリティ上NGです。 そこで、 「YAMLにはキーの定義のみ」「実体(値)はGitHub Secrets」 という役割分担を行いました。 Pythonスクリプト側で、YAMLの定義を見て type: SecureString ならば、対応する環境変数を読みに行く設計にしています。 命名規則: YAMLのkey: db/password → 環境変数名: SSM__DB__PASSWORD 環境ごとにSecureStringの値を分ける DBパスワードなどは環境ごとに異なる値を使うことが多いです。GitHub Actionsの Environments 機能を使えば、環境ごとに異なるSecretsを設定できます。 設定手順: GitHubリポジトリの Settings → Environments で環境を作成( dev , stg , prod ) 各環境のSecretsに SSM__DB__PASSWORD などを登録(値は環境ごとに異なる) ワークフローで environment: ${{ matrix.env }} を指定 これにより、DEV環境ではDEV用のDBパスワード、STG環境ではSTG用のDBパスワードが自動的に使われます。 補足:Parameter Store vs Secrets Manager 「機密情報なら Secrets Manager では?」と思う方もいるかもしれません。使い分けの目安は以下の通りです: Parameter Store (SecureString) Secrets Manager 料金 標準パラメータは無料 $0.40/シークレット/月 ローテーション 手動 自動ローテーション可能 向いているケース APIキーなど更新頻度が低いもの DBパスワードの自動ローテーションが必要な場合 多くのケースではParameter Store(SecureString)で十分で、Secrets Managerは「RDSパスワードの自動ローテーション」が必要な場合に検討してください。 :::message 補足:Secrets Managerの値も同様に管理できます。 cmd = [ "aws", "secretsmanager", "put-secret-value", "--secret-id", secret_id, "--secret-string", value ] subprocess.run(cmd, check=True) ::: Pythonスクリプト構成 aws_param_common.py - 共通機能 #!/usr/bin/env python3 """AWS Parameter Store 共通処理""" import os import sys import json import subprocess from typing import Dict, Any, Tuple import yaml def get_env_name() -> str: """環境名を取得""" env = os.environ.get("ENV_NAME") if not env: print("エラー: ENV_NAME 環境変数が設定されていません") sys.exit(1) return env def get_prefix(env: str) -> str: """環境に応じたプレフィックスを返す""" return f"/{env}/app/config/" def load_yaml_config() -> Tuple[Dict[str, Any], set]: """YAMLファイルを読み込む""" yaml_path = os.path.join(os.path.dirname(__file__), "..", "aws-params.yml") with open(yaml_path, "r", encoding="utf-8") as f: config = yaml.safe_load(f) yaml_keys = {param["key"] for param in config.get("parameters", [])} return config, yaml_keys def get_existing_params(env: str) -> Dict[str, Dict[str, Any]]: """AWS SSMから既存のパラメータを取得(ページネーション対応)""" prefix = get_prefix(env) existing_params = {} next_token = None while True: cmd = [ "aws", "ssm", "get-parameters-by-path", "--path", prefix, "--recursive", "--with-decryption", "--output", "json" ] if next_token: cmd.extend(["--next-token", next_token]) try: result = subprocess.run(cmd, check=True, capture_output=True, text=True) data = json.loads(result.stdout) params_data = data.get("Parameters", []) except subprocess.CalledProcessError as e: print(f"警告: パラメータの取得に失敗しました: {e.stderr}") return {} for param in params_data: key = param["Name"].replace(prefix, "") existing_params[key] = { "value": param["Value"], "type": param["Type"], "version": param.get("Version", 1) } next_token = data.get("NextToken") if not next_token: break return existing_params def get_param_value(param: Dict[str, Any], env: str) -> str | None: """パラメータの値を取得(SecureStringは環境変数、それ以外はYAMLの値を使用)""" # SecureStringの場合は環境変数から取得 if param.get("type") == "SecureString": env_var_name = "SSM__" + param["key"].upper().replace("/", "__") value = os.environ.get(env_var_name) if not value: print(f"警告: SecureString {param['key']} の環境変数 {env_var_name} が未設定") return None return value # 環境固有の値 env_values = param.get("environment_values", {}) if env in env_values: return str(env_values[env]) # デフォルト値 if "default_value" in param: return str(param["default_value"]) return None def validate_param(param: Dict[str, Any], env: str) -> Tuple[bool, str, Dict[str, Any] | None]: """パラメータのバリデーション""" key = param.get("key") if not key: return False, "keyが定義されていません", None value = get_param_value(param, env) if value is None: return False, f"{key}: 環境 {env} の値が定義されていません", None param_info = { "key": key, "value": value, "type": param.get("type", "String"), "description": param.get("description", "") } return True, "", param_info def update_parameter(param_info: Dict[str, Any], env: str) -> bool: """パラメータを更新""" prefix = get_prefix(env) full_name = prefix + param_info["key"] cmd = [ "aws", "ssm", "put-parameter", "--name", full_name, "--value", param_info["value"], "--type", param_info["type"], "--overwrite" ] if param_info.get("description"): cmd.extend(["--description", param_info["description"]]) try: subprocess.run(cmd, check=True, capture_output=True, text=True) add_tags(full_name, env) # タグを追加 return True except subprocess.CalledProcessError as e: print(f"エラー: {param_info['key']} の更新に失敗: {e.stderr}") return False def add_tags(parameter_name: str, env: str) -> bool: """パラメータにタグを追加""" cmd = [ "aws", "ssm", "add-tags-to-resource", "--resource-type", "Parameter", "--resource-id", parameter_name, "--tags", f"Key=Environment,Value={env}", "Key=SID,Value=backend-api" ] try: subprocess.run(cmd, check=True, capture_output=True, text=True) return True except subprocess.CalledProcessError as e: print(f"警告: タグの追加に失敗: {e.stderr}") return False ポイント: get_existing_params : ページネーション対応で50件以上のパラメータも取得可能 get_param_value : SecureStringは環境変数から、通常パラメータはYAMLから値を取得 update_parameter : パラメータ更新後に add_tags を呼び出してタグを付与 タグについて パラメータ作成時に、自動でタグを付与します。タグはAWSコンソールでの検索やコスト管理に便利なだけでなく、システムによってはタグがないとパラメータを読み込めない場合もあります。 タグ 値 説明 Environment dev , stg , prod 実行時の環境名が自動で入る SID backend-api サービス識別子(自分のサービス名に置き換えて使用) update_aws_params.py - 更新スクリプト #!/usr/bin/env python3 """AWS Parameter Store 更新スクリプト""" import sys import aws_param_common as common def update_parameters(): """パラメータを更新し、結果をレポートする""" env = common.get_env_name() print(f"=== 環境: {env} ===") print(f"プレフィックス: {common.get_prefix(env)}") print() config, yaml_keys = common.load_yaml_config() existing_params = common.get_existing_params(env) print(f"既存パラメータ数: {len(existing_params)}") print() updated_params = [] skipped_params = [] failed_params = [] for param in config.get("parameters", []): is_valid, error_msg, param_info = common.validate_param(param, env) if not is_valid: print(f"[スキップ] {error_msg}") continue param_key = param_info["key"] value = param_info["value"] # 既存の値と比較 if param_key in existing_params: if existing_params[param_key]["value"] == value: print(f"[スキップ] {param_key}: 値に変更なし") skipped_params.append(param_key) continue print(f"[更新] {param_key}: 値を更新します") else: print(f"[新規] {param_key}: 新規パラメータを追加します") # パラメータを更新 success = common.update_parameter(param_info, env) if success: updated_params.append(param_key) print(f" ✓ 完了") else: failed_params.append(param_key) print(f" ✗ 失敗") # 結果サマリー print() print("=== 結果サマリー ===") print(f"更新: {len(updated_params)} 件") print(f"スキップ(変更なし): {len(skipped_params)} 件") print(f"失敗: {len(failed_params)} 件") if failed_params: print() print("失敗したパラメータ:") for key in failed_params: print(f" - {key}") sys.exit(1) print() print("✓ 正常終了") if __name__ == "__main__": update_parameters() ポイント: 値が変わっていないパラメータはスキップ(無駄な更新を防ぐ) 更新結果を統計情報として出力 失敗時は終了コード1で終了 GitHub Actionsワークフロー name: Sync AWS Parameter Store on: workflow_dispatch: # 手動実行 jobs: sync-parameters: runs-on: ubuntu-latest strategy: matrix: env: [dev, stg, prod] environment: ${{ matrix.env }} # 環境ごとのSecretsを使用 steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: pip install pyyaml - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 - name: Sync Parameters env: ENV_NAME: ${{ matrix.env }} # SecureString用(環境ごとのGitHub Secretsから取得) SSM__DB__PASSWORD: ${{ secrets.SSM__DB__PASSWORD }} SSM__API__SECRET_KEY: ${{ secrets.SSM__API__SECRET_KEY }} run: | cd .github/scripts python update_aws_params.py ポイント: strategy.matrix で複数環境を並列実行 environment: ${{ matrix.env }} で環境ごとのSecretsを使用(devとprodで異なるDBパスワードなど) SecureStringの値は環境変数経由でスクリプトに渡す 値に変更がないパラメータは自動的にスキップされる 使い方 1. パラメータの追加・変更 .github/aws-params.yml を編集してPRを出すだけです。 parameters: # 新しいパラメータを追加 - key: feature/enable_payment_v2 description: "新決済システムの有効化" environment_values: dev: "true" stg: "false" prod: "false" 2. 全環境への反映 GitHub Actionsページを開く Sync AWS Parameter Store を選択 Run workflow ボタンをクリック 全環境に並列で反映される 3. SecureStringパラメータの追加 YAMLに定義を追加: - key: payment/api_key description: "決済APIキー" type: "SecureString" GitHub Environmentsに値を登録: Settings → Environments → 各環境(dev, stg, prod)のSecretsに登録 Secret名: SSM__PAYMENT__API_KEY 値: (環境ごとに異なる実際の値) ワークフローファイルの環境変数セクションに追加: env: SSM__PAYMENT__API_KEY: ${{ secrets.SSM__PAYMENT__API_KEY }} 4. 実演 GitHub Actions画面から今回作ったアクションを選んで、起動します。 アクションが正常終了したことを確認します。各環境が並列で動作した事がわかります。 AWSのパラメータストア画面を開いて確認してみます。パラメータが登録されています。成功です。 導入効果 具体的な効果 作業時間 : 環境数 × 5分 → ワンクリック (10環境なら50分削減) 更新漏れ : 月数回発生 → ゼロ 確認作業 : AWSコンソールを開く → YAMLを見るだけ まとめ クラウド時代の「環境が増えすぎ問題」は、多くの現場で直面している課題だと思います。 今回紹介したアイデアのポイントは: YAML可視化 - 全環境のパラメータを1ファイルで管理 ワンクリック同期 - GitHub Actionsで自動反映 SecureString対応 - 機密情報も安全に管理 特別な技術は使っておらず、 GitHub Actions + Python + AWS CLI だけで実現できます。 Parameter Storeの管理で困っている方、環境が増えて運用が大変になっている方の参考になれば幸いです。 最後までお読みいただき、ありがとうございました🙇‍♂️
アバター
この記事は KINTOテクノロジーズ Advent Calendar 2025 の10日目の記事です🎅🎄 はじめに KINTOテクノロジーズのCloud Infrastructure G(CIG)でInfrastructure Architectを担当している劉(YOU)です。11月から技術広報 Gも兼務することになりました。 2025年11月、弊社は「 CloudNative Days 2025 Winter 」に、スポンサー企業として参加しました。今回のイベントでは、弊社の社員2名が登壇し、私が所属しているCloud Infrastructure G・技術広報 Gがスポンサーブースを運営し、多くのメンバーが参加者として技術セッションに参加するなど、組織全体で積極的に関わることができました。 CloudNative Daysは、日本国内のクラウドネイティブコミュニティにおいて最大規模のイベントです。今回の参加を通じて、私たちは技術的な知見を深めるだけでなく、CloudNativeという概念そのものについて改めて考える機会を得ることができました。 本記事では、登壇者、ブース運営担当者、そして一般参加者、それぞれの視点から見たイベントの様子と、そこから得られた学びを共有します。 登壇者インタビュー Cloud Infrastructure G、古代さん:「 モビリティプラットフォームの未来を築くクラウド基盤 」 登壇に至った経緯 古代さん :「入社して1年経っていない状態で、組織の歴史を知らない、技術選定の理由を知らない。自分が経験していない、実体験がないことをどう伝えればいいのか、それが一番大変でした」 当初は別のメンバーが登壇予定だったところ、都合により急遽登壇することになりました。2025年1月に入社したばかりで、組織の歴史や技術選定の背景を十分に把握していない状態でのチャレンジです。 テーマ選定の背景 古代さんはCloudNativeというテーマに対して悩みました。CloudNative関連の資料を探したら、多くの登壇者がKubernetesやコンテナなど、技術的に深い内容を発表する中、弊社は主にECSを利用していてKubernetesは限られた所だけ使っていません。しかし、その悩みこそが、同じような状況にいる多くの人々と共有できる価値だと気づきました。 古代さん :「CloudNativeはハイレベルに感じるかもしれませんが、そこに至るまでは組織としての試行錯誤が必要です。技術で尖ることも必要ですが、組織としてCloudNativeな文化を作ることが重要です。ベストプラクティスはありません」 最も伝えたかったのは、 CloudNativeは手段であり、目的ではない ということです。ビジネスに向き合いながら、適切な技術を選定していく。その試行錯誤の歴史をありのまま伝えることで、同じように悩んでいる人たちが前進できるきっかけになればと考えました。 当日の反応 会場Cの66名規模の会場は、ほぼ満席でした。オンラインで他のセッションを聞いていた方が、途中からこのセッションに移動してくるケースも多く見られました。 古代さん :「頷きながら聞いてくださる方がいて、スライドを写真に撮ってXに上げてくれる人もいました。技術がかなり重視されそうなイベントにおいて、自分のテーマに興味を持ってもらえたのは印象的でした」 質問もマイクで2名、「Ask the Speaker」で3名が訪れ、初めての登壇としては大きな手応えを感じることができました。特に印象的だったのは、Terraformの作り方やプラットフォーム組織の意思決定についての質問でした。これらは正解のない問いですが、 みんなそういうところで悩む ということを改めて実感します。 得られた学び 今回の発表は40分に至る長めのことでして、登壇に関する資料作成や発表練習に追われた古代さんは日々のアウトプットの重要性を痛感しました。 古代さん :「日々発表していれば、もっとスライド作りが簡単だったと実感しました。テックブログなどを書いていれば、それを応用して活用できるなと。アウトプットを頑張ろうと決意しました」 また、AIの使い方についても重要な気づきがあります。最初にAIとスクリプトでトークスクリプトを作ろうとして失敗した経験から、「どういうメッセージを伝えたいのかを固めた上で、どう表現すればいいかをAIに聞くのが重要。最初からAIに聞くのは絶対にダメ」という教訓を得ました。 次回への展望 次回は20分程度の短い登壇で回数を重ね、技術を中心にしたネタの発表もチャレンジしたいと考えています。また、せっかくのコミュニティイベントなので、グループワークや相互学習を通じて、参加者とよりネットワークを深めたいという思いも語ってくれました。 古代さん :「来年はCloudNative会議が開催されます。KINTOテクノロジーズはトヨタグループの中でも先進的にクラウド活用を進めている組織です。ハイレベルなものがあるわけではありませんが、ハイレベルのものを一緒に作ることはできるフェーズになっています。一緒にコラボしたり、勉強会をしたりする機会が増えると嬉しいです」 Platform G、李さん:「 AI Agentで切り開くアラート対応の新時代 」 CFP応募の決断 李さんは、採択される可能性は低いと考えながらも、「とりあえず出してみよう」という気持ちでCFPを応募しました。応募理由は明確です。会社の外部発信力を高め、グループ内でのエンジニア採用に貢献し、外部コミュニケーションの機会を得るためです。スポンサーワークとは完全に別枠での応募だったため、採択されたことに驚いたそうです。 資料作成での苦労 李さんが資料作成に苦労したのは、対象者のレベル設定でした。 李さん :「詳しい人もいれば、初心者もいる中で、どのレベルに合わせるか悩みました。知らない人が見ても理解しやすい内容にしたいが、軽すぎる内容では深い知識を求める人に物足りない。このバランスを取ることが最も難しかったです」 特にストーリーテリングにも力を入れました。「こういう課題があって→こう対応して→結果こうなった→現在はこうなっている」という流れを意識し、話の流れが自然になるよう、全体構成を何度も見直しました。資料作成には約1週間以上、継続的に質を上げるために原稿やプレゼンを整えました。 想定外の出来事 当日、最も不安だったのは、PowerPointのスクリプト機能が聴衆側の画面に表示されるかどうかです。40分の発表で台本なしは厳しいため、昼休みに自ら会場に行って確認を依頼しました。 李さん :「登壇者向けの情報提供として、到着時間やHDMI接続などの基本情報はありましたが、リハーサルや台本表示確認などの詳細な案内が必要でした。次回登壇する社内メンバーには、事前に会場で確認することをアドバイスしたいです」 参加者の反応 発表は全体的に好評で、同じ発表者である古代さんからも「今日のセッションで最も印象的だった」とのコメントをいただきました。発表終了後にも4〜5名が質問に来て、AI Agentに対する関心の高さを実感できたと話ました。 特に印象的だったのは、コンテキストエンジニアリングに関する質問です。AIに必要な情報と、人間にだけ必要でAIには不要な情報をどう区別しているか。この質問に対しても、現在検討中の対策を共有してくれました。 李さん :「LLMモデルのインプット制限の問題があり、コンテキストが大きくなりすぎると制限を超えてしまいます。シングルエージェント構成で対応予定で、LangChainの新機能を活用し、履歴を要約・圧縮する方式を検証中です。この改善内容を来年のカンファレンスや技術記事で発表したいと考えています」 得られた学び 李さんにとって、大規模なオープンコミュニティでの登壇は初めてだったので得られた事が多かったそうです。 李さん :「緊張はしましたが、思ったほど恐れる必要はないと感じました。やってみたい人がいれば、積極的にトライすることをお勧めします。他の登壇者や参加者から大きな刺激を受け、技術的なモチベーションが向上しました」 技術的な収穫としては、インシデント管理のSaaSやサイバーエージェント大山さんのLoki・Prometheusに関する発表が参考になったそうです。また、Xのフォロワーも数十人増加し、コミュニティでのつながりが広がりました。 次回への展望 時間管理については反省点があります。練習では台本を見ながら話すため早く終わりすぎないか心配でしたが、本番では緊張して言葉が出てこず、逆に時間が足りなくなり、1ページスキップする必要がありました。 次回は、コンテキストエンジニアリングの改善やAgentの評価方法について発表したいと考えています。 李さん :「Agentの効果をどう評価するか、評価基準の作成が難しい。同じアラートでも原因が異なる場合があり、正解データの作成が困難です。これは業界全体で共通の課題として認識されています。コンテキストエンジニアリングは最近注目されている分野で、ベストプラクティスがまだ確立されていません。多くの人が知りたがっている内容だと思います」 李さんからのアドバイスは明確です。 李さん :「まずは応募してみることが重要。採択されてから、未来の自分が何とかしてくれます。個人的な刺激とモチベーション向上、会社のプレゼンス向上、双方にメリットがある活動です」 インタビュー外の登壇 Cloud Infrastructure G マネージャーの辻さんもTOYOTAグループの技術コミュニティの紹介で登壇しました! Cloud Infrastructure G、辻さん:「 トヨタグループのエンジニアコミュニティ TURTLEの紹介! 」 ブース運営インタビュー 今回のブース運営には、弊社の色んな方々が手を貸してくれました。Cloud Infrastructure G・Platform G・人事 G・技術広報 G、社内の様々なGの協力の上、盛り上がったブース運営を遂行し、無事に終わらせることができました。 ブース運営のインタビューの場合、ディスカッション方式で進みまして、 Cloud Infrastructure G(CIG):古代さん、白井さん、安田さん、松尾さん 技術広報 G:村山さん が参加してくれました。 準備段階での課題 CIGでのブース運営は初めてで何をしたらいいのかわかりませんでしたが、弊社では技術広報 Gが社内外を問わず、発信力を高める組織として今回のようにイベントの企画から振り返りまで全力で手伝ってくれます。 今回も、技術広報 Gの村山さんがブース運営の準備段階からサポートをしてくれました。マネージャの辻さんと一緒に調整して成功的にブースの運営ができたキーポイントでした。 村山さん :「事前にマネージャーの辻さんと相談し、欲しい情報などを軽く伝えました。CIGの定例にて色々話を進めてくれて、シフトもいつの間にかできており大変助かりました。」 村山さん :「また、週末にスポンサーをしたカンファレンスにてシステムアーキテクチャ図のボードを展示したら評判が良く、来場者と多くのコミュニケーションを取ることができていたので、開催日前日ながら『なにか掲載できるアーキテクチャ図とかありませんか?』と提案したところ、すぐにベストなアーキテクチャ図を提供してくれました。印刷もギリギリ間に合いブースに置けましたが、コミュニケーションのきっかけになっていたので用意できてよかったなと思いました!」 どういう物がブースに惹かれることに役立つかは色んなブース運営経験なしでは出来ないことだったので、本当に貴重なアドバイスだと思います。 クラウドインフラGでは今回初めてブース運営参加した人も多く、心構えのような心理的な準備が大半でした。 白井さん :「明るく元気に、ネガティブな印象を抱かせないように意識しました。ブースに来てくれた人に対して良い印象を持ってもらうようにしてほしかったです。」 白井さんが言うようにブースに訪ねてくれる方々にポジティブな姿を見せるために努力を注ぎました。 当日の様子と気づき クラウドインフラGがCloudNative Days 2025 Winterで伝えたかったポイントは2つありました。スポンサーセッションに登壇した古代さんの話を聞くと、 古代さん :「1つ目は、クラウドインフラグループ[^1]が何をやっているかをちゃんと知ってもらうこと。2つ目は、グループの内にあるソリューションチームの活動内容について広報です。弊社は自社サービスだけでなく、トヨタグループ全体に対して多様な活動をやっている。そこを来場者のの皆さんが少しでも知ってもらいたかったです」 具体的には来場者に対して、ブースで用意されていた質問ボードに記載されている、 Q3.KINTOテクノロジーズを知っていますか? この質問を通してコミュニケーションをとりながら、クラウドインフラグループとしての活動と会社の方向性を広く認知してもらうことができました。 また、他にも重要な気づきがありまして、 古代さん :「イベント参加者全員がブースに来てくれるわけではありませんが、ブースに来て話を聞いてくれる人は、少なくともKINTOテクノロジーズに興味を持ってくれています。また、CloudNative自体に関わっていない人も一定数いることに気づきました。イベントタイトルに対してのギャップとして印象的でした」 最初はCloudNativeのイベントなので、CloudNativeエンジニアが多く参加する印象がありましたが、既にCloudNativeな活躍を実施されているエンジニアより、CloudNativeをこれから目指したいと考えている方もたくさんいる事が分かりました。実際、会場ではアプリ開発者や営業・デザイナー・記者・学生などの様々なポジションの方が参加されていました。 安田さん :「もっとたくさんの人にKINTOテクノロジーズを知ってもらおうと思っていましたが、来場していただいた方の半数は既にKINTOテクノロジーズを知ってくれていました。直近実施したイベントの 開発生産性カンファレンス や 技術書典 でKINTOテクノロジーズを知っていただいた方が多くて印象的でした」 また、CloudNativeDaysの参加者の傾向としてインフラ担当者がかなり少ないことも分かりました。アプリ担当者がインフラも兼任していることが多く、アプリコードを書きながらインフラのリソース作成やセキュリティ対応を実施するなど非常に大変だと言っていました。KINTOテクノロジーズは我々のように専門のインフラ組織やSRE/セキュリティ組織が存在しており、役割が細分化されているので、各々の専門領域に注力できているのだなと改めて強く感じてます。 質問ボードを通じて、様々な人の考え方を聞けたことが良かったとの意見もありました。質問の内容についても来場者のバックグラウンドや性格・気になるポイントによって変更したりしました。 Q1.あなたのクラウドスタイルは? → 業務スタイル & 開発スタイル Q2.あなたにとって「良いインフラ」とは? → 「良い仕事」 & 「良いシステム」 松尾さん :「ボードにある質問を深掘りして、人の考え方とか重要視していくことを聞けてよかったなと思ってます。印象に残っているのはAWS以外のクラウドサービスを触ってる人が意外と多くて、自分はAWSしか触ってないからマルチクラウドを少しでもやりたくなりました。」 様々なロールの方や、技術に対して高い興味ある方が多く参加されており、 白井さん :「多種多様なロールの方に来場していただきましたが、やはり私の専門領域以外のロールの方と話を広げるのが難しかったです。今後は幅広いロールの方と話ができるように自分の土台(知識・話術)を作っていきたいと思います」 CloudNativeだけどブース運営に関しては、単純にインフラの専門家のポジションではなくあらゆるロールの来場者を考慮して対応ができるように体制を整える必要があることを感じてます。 成果と改善点 今回の最大の成果として次のような会話があって、 古代さん :「インフラチームとカイゼンチーム、ほぼ全員がグループの運営側としてイベントに参加しました。なかなかない経験です。みんなで同じイベントの経験を共有できたのはありがたいです。イベント運営しながら『こうだよね、ああだよね』という学びを共有できました」 白井さん :「チームのみんなでイベントを開催することによって、よりチームの一体感が増しました。また、他社の人たちが実践している内容を伺うことによって新たな気づき得ることもできました。」 個人でイベントを参加することもいいことだと思いますが、チームとして参加してたらお互いが同じマインドセットを成立することができます。チームの成長と意識の向上、一緒にやることで連続性を持った技術発信の道を改めて整備ができたと思います。 一方で、改善点もありました。今回は参加者として興味あるセッションを聞いて知見を広める目的もありましたので、事前にグループ内でシフトを決めていました。 松尾さん :「シフト表を作成していましたが、自分の番になりブースに向かったらみんなブースにいたので『あれ?』と思いました」 古代さん :「次回ブースをやるときは3人はブース内で、他の人はセッション聴講してインプットできるようにした方がいいかと。セッションでブログのネタを探すなど、効率化を考える必要があります」 課題として認識したことはシフトの問題より、時間の効率化になります。CIGはブース運営者・参加者の二つの立場でイベントに参加したため、ブース運営とセッションを聞いて自らの収穫を得る間のバランス調整が難しかったと思います。 それでも、ちゃんと振り返りをしながら個々で考えていた課題を明確にすることができました。質問ボードでKINTOテクノロジーズを知っていたという答えが多かったことは、長く広報活動を実施した成果であり、イベントの運営は単発で終わることではない証でもあります。クラウドインフラGもKINTOテクノロジーズの一員として技術発信を継続していきますので、今回の参加は今後の活動のエッセンスになりました。 CloudNativeの本質 最後に、みなさんに記載いただいた質問ボードの結果を深掘りしました 白井さん :「『良いインフラとは?』という質問に対して、『愛せること』と回答した人が多かったです。深掘りしてみると、自社サービスを愛せるまで理解することが、CloudNativeという言葉の本質の理解を深めることの1つなのかもしれません」 古代さんからは、登壇でも発表したメッセージを改めて強調しました。 古代さん :「CloudNativeを最初から完璧にできている組織はありません。色々なグラデーションがあります。技術だけでなく、文化や組織も含めて作っていく必要があります。スポンサーブースで色々な企業と話しながら、改めて実感しました」 参加者インタビュー Platform G、島村さん 島村さんは、同じグループの李さんの登壇を見るため、そして自身もCFPを出していたため、どちらにしろ参加する予定でした。そして、最近チームでKubernetesやEKSを触り始めたこともあり、技術的な関心も高まっています。 印象に残ったセッション: 任天堂の事例 島村さんが最も印象に残ったのは、任天堂のセッションでした。 島村さん :「オンプレ環境の事例ですが、この文化を取り込みたいと思いました。真面目に毎日1回フルでリグレッションテストを回していて、バグ報告以外の目的でもテスト結果を使っていることに驚きました。」 特に興味深かったのは、テスト結果の多目的活用です。 島村さん :「UI的な観点で、デザイナーが表面上のデザイン単体ではなく『ビルド結果の画像から考えると、こうした方が(周りなどの雰囲気と)マッチする』という判断材料となるそうです。過去からのイベントの流れがあり、その流れで自然かどうかも分かる。声優のアテレコにも使用しているそうです。自動テストをバグを見つけるだけのものではなく、他の用途にも使う。テストの結果を使ってプロアクティブに多方面で改善点を見つけて改善していく文化です。」 そして、任天堂の事例と弊社との違いについても考察してまして、 島村さん :「KINTOにはこの文化がまだ足りないと思いました。できるようにして、気づいて、直していくという感覚が遠いです。重くなったウェブサイトは改善していく側面でも必要ですし、KINTOのサービスをより良く提供する面もあると感じました」 技術的な収穫 島村さんは、いくつかの技術的な収穫を話してくれまして、 Alloyのログ二重転送の原因の気づき :最近ブルーグリーンデプロイをした時、Alloyのログが二重転送されていた原因だと思われる事象に似ていると気付いて、調査のインプットとしました。 Dockerビルドのセキュリティ :ビルドしたコンテナイメージがマルウェアに汚染されていないか、署名や検証のフレームワークがあることを知りました。昨今の情勢もありましたので、セキュリティチームに情報を共有しました。 RCAの優位点の再確認 :インシデント管理のSaaSは、インシデント管理のやり方だけをAI化していますが、弊社では RCA で一気通貫で作っています。やはり全部統一して一気通貫しないといけないと、RCAの優位点・良かった点を見直せました。 逆に、いくつかのセッションに対して疑問を感じたことも話してくれました。 CloudNative技術が課題を解決する手段ではなく、CloudNative技術を使うための目的になっていることもあった 知識として得られるような内容ではなく、それぞれのケースに基づいた話を聞きたかった 次回への展望 島村さんは、次回への改善点と展望を語りまして、 島村さん :「早めにイベントのセッション登録をして、自分が聞きたいセッションを確実に聞けるようにしたいです。また、参加して得た情報を個人の中に留めず、他のメンバーが見れるようにする。こういうことがある、事例があると知ってもらわないと、参加した時間がもったいない。知識は、広がらないとあまり意味がないと考えています。」 登壇についても意欲を見せてくれました。 島村さん :「CFPを2回落ちているので、次は是非登壇したいです。ただ、ネタが問題です。RCAについては既に喋り、文化論も Platform Engineering Meetup で喋っています。新しいネタを見つける必要があります」 まとめ 我々はCloudNative Days 2025 Winterへの参加を通じて、多くの学びと気づきを得ることができました。 技術と文化の両輪 登壇者の古代さんと李さんは、それぞれ異なるアプローチでCloudNativeを語りました。古代さんは組織文化の観点から、李さんは技術的な実践の観点からです。しかし、両者に共通していたのは、 CloudNativeは手段であり、目的ではない というメッセージです。 ブース運営を通じて、私たちは「CloudNativeを最初から完璧にできている組織はない」ことを再確認しました。技術だけでなく、文化や組織も含めて作っていく必要があります。 参加者の島村さんが任天堂の事例から学んだように、CloudNativeは技術ではなく文化です。プロアクティブに改善点を見つけて改善していく文化、テスト結果を多目的に活用する発想、そして何より、自社サービスを「愛せる」まで理解すること。これらがCloudNativeの本質なのかもしれません。 チームの一体感 今回のイベントで最も大きな成果の一つは、インフラチームと改善チーム、ほぼ全員がグループの運営側としてイベントに参加できたことです。みんなで同じイベントの経験を共有し、学びを共有できたことは、チームの一体感を大きく高めました。他のグループの方も助けてくださり、とても良い時間を過ごす事ができました。 継続的なアウトプットの重要性 登壇者の両名が共通して感じたのは、日々のアウトプットの重要性でした。40分の発表を準備する大変さを経験し、普段からテックブログや勉強会での発表を通じて、継続的にアウトプットする習慣の必要性を実感しました。 また、島村さんが指摘したように、イベントで得た情報を個人の中に留めず、チーム・組織で共有することも重要です。知見を広げることで、組織全体の成長につながると信じてます。 次回への展望 2026年には、CloudNative会議(Platform Engineering Meetup、SRE会議、CloudNativeDaysの合同イベント)が開催されます。また、JAWS Daysへの出展も予定しています。 CloudNativeに悩む皆さんへ、古代さんからのメッセージをお伝えします。 「技術に振り回されないでください。ビジネスの成功、ビジネスの先にあるミッションの達成のために、一緒に悩みながら文化づくり、組織づくりを頑張りましょう。CloudNativeは手段であり、目的ではありません」 そして、李さんからのアドバイスも忘れないでください。 「まずは応募してみることが重要です。恐れずに、まずCFPを出してみる。採択されてから、未来の自分が何とかしてくれます」 今回のイベント参加を通じて得た学びと気づきを、今後の組織づくり、文化づくり、そして技術的な挑戦に活かしていきたいと思います。コミュニティの皆さんとの継続的な交流を通じて、共に成長していけることを楽しみにしています。 [^1]: Cloud Infrastructure Gでは記事を作成する時点で、インフラチームとカイゼンチーム、ソリューションチームの三つのチームが存在します。
アバター
Introduction Hello, and thank you for reading! I'm Nakamoto, a frontend developer at KINTO FACTORY . This time, rather than discussing technical topics, I'd like to share my experience taking paternity leave when my son was born in August this year. I'll cover the handover process at the team level and my thoughts after returning to work. Consulting with My Manager In April of this year, I was assigned as the team leader for the frontend team within the FACTORY E-commerce Development Group. At first, I felt a bit hesitant about being away from work for an extended period, but when I consulted with my manager, he wholeheartedly encouraged me to take paternity leave. I was told that the first few months after birth are particularly demanding for mothers, both physically and mentally, so I should try to support my wife as much as possible. So I applied for about two months of paternity leave, with the plan to reassess after one month whether I could return early. Handover Items Now, when it came to handing over my daily responsibilities, in addition to my regular frontend development work, as team leader I handled: Reviewing architecture for new projects, making directional decisions, and coordinating with related departments 1-on-1 meetings with each team member Semi-annual reviews and evaluations with each team member Recruiting activities Let me go into detail on each of these. Reviewing architecture for new projects, making directional decisions, and coordinating with related departments After regularly checking the roadmap with the PdMs and my manager for late August when my leave was scheduled to start, it turned out there weren't any major new projects coming up and most were already in progress. So I started documenting the projects I had been handling in Confluence, making it a habit to briefly summarize things like "who I discussed this with and what was decided" and "what I've completed so far." 1-on-1 meetings with each team member Semi-annual reviews and evaluations with each team member Next was the management area. One of my missions as team leader was to review and evaluate each team member's progress on a semi-annual basis. That review period was going to overlap directly with my leave period, so from the start of the April term, I had each team member set their goals in advance to make the reviews go smoothly. In practice, each member would record their goals and write monthly updates in Confluence about "what they accomplished and what they plan to do next month," which served as discussion topics for our 1-on-1s. I also made notes in that same Confluence page about things I noticed and the efforts I appreciated, so I could provide feedback during reviews. Since I was planning to take leave around the time of the birth, I figured that if my leave overlapped with the evaluation period, I could simply hand over that Confluence page to my manager, allowing him to quickly review each member's goals and achievements. Recruiting activities For this, I basically asked my manager to take over. However, I wanted the development team members to get a sense of candidates' technical skills and communication abilities from the interview stage, so I had them join interviews to check on communication and technical skills at the team level. During My Leave With most of the handover preparations in place, on August 24th, our healthy baby boy was born! I took three days of special leave, going back and forth between local government offices and the hospital. On the day before my paternity leave officially started, I went to the office just once to return my company equipment (PC, phone, employee badge, etc.). From there, for about two months, I stepped back from work and fully committed to taking care of my baby! Childcare Is Tough The FACTORY E-commerce Development Group has plenty of experienced dads, and before the birth I'd heard all kinds of stories, but as expected, the first month was really hard. My wife and I split childcare and sleep into half-day shifts and I handled childcare from morning until night. My wife suggested this approach, saying it would help me maintain my sleep cycle for when I returned to work, so I was basically up during the day and sleeping at night. With feeding, diaper changes, soothing the baby, and bath time coming at me almost by the minute, I barely had any time to think about myself. I don’t think work even crossed my mind at all during the first week or two. Still, the Service Is on My Mind About a month into my leave, I was able to talk with my manager and get updates on project progress and the team's status. After a month of hardly going out and barely talking to anyone, it felt like a huge mental reset. Also, by this point I'd gotten quite used to childcare, and my son was settling into longer sleep periods, so I remember occasionally checking on service updates. KINTO FACTORY releases new products and features on Wednesdays, so every Wednesday I'd visit the site to see "what's new this month?" The Return And so, after 65 days of paternity leave, I returned to work in November. There weren't any major changes to the group members, and some ongoing projects had been pushed back. Overall things hadn't changed dramatically from before my leave, so I was able to return smoothly. However, since my wife suddenly had to handle childcare solo during the day, I try to reduce her burden by going to work as early as possible in the morning and coming home as early as possible in the evening. This is made possible by our full-flex system. As long as I coordinate meetings and get agreement from the team, I can freely adjust my working hours. Conclusion By dedicating myself to childcare during the first two months, I was able to witness my son’s growth up close, from his very first smiles to his little coos and all the small changes he made day by day. Being so involved during this period felt like an incredibly precious experience. I was only able to have this experience because my manager and everyone in the group warmly sent me off on paternity leave. I'd like to take this opportunity to express my gratitude. At KINTO Technologies, I believe it's an environment where men can take paternity leave without difficulty. In fact, I've heard that many male engineers across different divisions have taken paternity leave just this year alone (there are many kids the same age as my son in the company!). I hope this is helpful for anyone preparing to take paternity leave or getting ready to return to work. Also, the article below introduces a day in the life of an experienced dad in the same FACTORY E-commerce Development Group, so please check that out too! A Must-Read for Parent Engineers! A Day in the Life of a KTC Dad Engineer
アバター
この記事は KINTOテクノロジーズ Advent Calendar 2025 の9日目の記事です🎅🎄 初めに こんにちは。11月に KINTOテクノロジーズ に入社した辻勝利です。 私は20年以上、デジタルアクセシビリティの分野で働いてきたエンジニアで、生まれたときから全盲の視覚障害者です。 この記事では、なぜ視覚障害者の私がモビリティを専門とするKINTOテクノロジーズに転職したのか、そして今後どんな未来を目指しているのかをお話しします。少し未来を見据えた、夢のような話になるかもしれませんが、ぜひ最後までお付き合いください。 視覚障害者と「移動」の課題 私は長崎県佐世保市で生まれました。長崎といえば坂の町。電車よりもバスが普及していましたが、私の家はバス停からも距離があり、視覚障害者が独力で日常生活を送るのは現実的ではありませんでした。 子供のころ、各家庭には自家用車があり、移動は車が中心。私が移動するには、家族に車を運転してもらうしかありませんでした。 30年ほど前に上京したとき、駅までの道を覚えれば電車で自由に移動できることに感動したのを覚えています。それほど、地方での生活は私にとって「移動の自由」がないものでした。 今住んでいる場所も駅から遠く、視覚障害者には不便な環境です。しかし、リモートワークで仕事をしながら、窓から聞こえる鳥の声に耳を傾ける生活はとても心地よいものです。視覚障害者の知人からは「不便ではないか」と心配されますが、3年住んでみて、日常生活に大きな不便は感じていません。 自動運転への憧れ 皆さんは「車の自動運転」にどれくらい興味がありますか? 私はこの場所に引っ越してから、自分の意思で自由に移動できる可能性を秘めた自動運転に強く惹かれるようになりました。視覚障害者である私にとって、自動運転は単なる技術ではなく、「移動の自由」を取り戻す夢です。 きっかけは自動運転タクシーの動画 初めて視覚障害者が自動運転車に乗る様子を見たのは、2012年にGoogleが公開した動画でした。当時は「本当に実現できるのだろうか?」と半信半疑でした。 https://www.youtube.com/watch?v=cdgQpa1pUUE しかし2024年、自動運転タクシーに視覚障害者が乗車する動画を見て驚きました。スマートフォンを操作し、クラクションの音で車の位置を確認して乗車する仕組み。しかも、アメリカの公道で実際に走っているのです。 この瞬間、「自分も試してみたい」と強く思いました。 https://www.youtube.com/watch?v=4OPiC-zXtJk&list=PLzaPe49p32aZ7SPFDhvgJV-cOpskQ5VYu&index=7 「KINTOテクノロジーズであれば、自動運転の技術にかかわることができるかもしれない。いつかは自分も自動運転にかかわれるような仕事がしたい。」 これが、私がモビリティカンパニーで働きたいと考えたきっかけです。長年培ったアクセシビリティの知識を活かし、視覚障害者の移動に貢献したいと考えています。 KINTOテクノロジーズで目指すこと 自動運転車が視覚障害者にとって当たり前になる未来を目指すとしても、すぐに実現できるわけではありません。多くの人にとって「視覚障害者」と「車」は最も縁遠い組み合わせでしょう。 前述のように、視覚障害者が誰かに車を運転してもらって目的地に行くことは想像できますが、単独で車に乗車して目的地まで行くことはあまり想像できないのではないでしょうか? しかし、このようなニーズは確かにあって、特に地方で暮らす視覚障害者は自分だけで行きたいところにいつでも出かけられるような未来を夢見ています。 公共交通機関を利用すれば目的地の近くまで移動することはできますが、そこから目的地を探して移動することは容易ではありません。 例えば、歩行者用のGPSナビゲーションを使って移動したとしても、目的地が近づくと「まもなく目的地付近です」という案内とともにナビゲーションは終了してしまいます。 そこからのほんの数メートルが、見ることのできない視覚障害者にとっては大きなハードルとなることがあるのです。 このように、「視覚障害者」と「移動」に置き換えれば、目的を達成するために移動することは誰もが容易に理解できるかと思います。その手段が「自動運転車」になる未来を作るために、私は次のことに取り組みます。 1. アクセシビリティを組織の当たり前にする モビリティ業界では、アクセシビリティの重要性はまだ十分に認識されていません。私は社内で丁寧に対話し、 なぜアクセシビリティが必要なのか チームで何から始めるべきか どんなゴールを目指すのか を一緒に考えていきます。 皆さんとともに活動し、これまで想定されていなかったかもしれないユーザーのニーズや、どんなことに不便を感じたり、どんなふうに課題を解決しているのかを見ていただくことで、「アクセシビリティは自分たちには関係ない」と思われない組織にすること。それが最初の1年の目標です。 その結果、KINTOテクノロジーズが取り組む様々な活動のアクセシビリティが向上し、新たなユーザーにリーチできて、ゆくゆくはそれが組織の価値になり、社会を変えるような取り組みになればいいなと考えています。 2. 障害者が働き続けたい組織を作る 50人以上の企業には障害者雇用が義務付けられていますが、実際には「どう接すればいいかわからない」「どんな仕事を任せればいいのか想像できない」という声もあります。 定型化された仕事だけを任され、企業の中で孤立してしまうケースも少なくありません。 KINTOテクノロジーズには私のほかにも障害のある社員がいます。私の役割は、私たち障害者が組織で働く姿を見てもらい、同僚として共に会社の目標に向かって進める環境を作ることです。 そして、私たち自身が「ここで長く働き続けたい」といえるような組織を作っていきたいと思っています。 「組織の内側からしか変えられない価値」を最大化し、KINTOテクノロジーズの発展に貢献していきます。 最後に 視覚障害者にとって「移動の自由」は人生を変える力を持っています。私はKINTOテクノロジーズで、その未来を現実に近づけるために挑戦します。 この取り組みに共感していただける方がいれば、ぜひ一緒に「移動の未来」を考えていきましょう。
アバター
This article is for Day 9 of the KINTO Technologies Advent Calendar 2025 🎅🎄 Introduction Hello. I'm Katsutoshi Tsuji, and I joined KINTO Technologies in November. I'm an engineer who has worked in the digital accessibility field for over 20 years, and I have been completely blind since birth. In this article, I'll share why I, a visually impaired person, chose to join KINTO Technologies, a company specializing in mobility, and what kind of future I'm working toward. This might sound like a futuristic dream, but please stick with me to the end. The Challenges of Mobility for Visually Impaired People I was born in Sasebo City, Nagasaki Prefecture. Nagasaki is known as a city of hills. Buses were more common than trains, but my home was far from the bus stop, making it unrealistic for a visually impaired person to live independently. When I was a child, every household had a car, and transportation centered around driving. The only way I could get around was to have my family drive me. When I moved to Tokyo about 30 years ago, I remember being moved by the realization that once I memorized the route to the station, I could travel freely by train. That's how much my life in the countryside lacked freedom of movement. The place where I currently live is also far from the station, which is inconvenient for visually impaired people. However, working remotely while listening to the birds singing outside my window is a very pleasant lifestyle. Some of my visually impaired acquaintances worry that it might be inconvenient, but after living here for three years, I haven't experienced any major difficulties in daily life. My Dream of Autonomous Driving How interested are you in autonomous driving? Since I moved here, I have found myself increasingly fascinated by autonomous driving and the possibility it offers: the freedom to move on my own terms. For me, as a visually impaired person, autonomous driving is more than just technology. It is a dream of reclaiming freedom of mobility. A Video of an Autonomous Taxi Sparked My Interest The first time I saw a visually impaired person riding in an autonomous vehicle was in a video released by Google in 2012. At the time, I was skeptical, wondering if it could really become a reality. https://www.youtube.com/watch?v=cdgQpa1pUUE However, in 2024, I was amazed when I saw a video of a visually impaired person riding in an autonomous taxi. The system allows passengers to operate their smartphone and locate the vehicle by the sound of its horn. What's more, it was actually running on public roads in the United States. At that moment, I strongly felt that I wanted to try it myself. https://www.youtube.com/watch?v=4OPiC-zXtJk&list=PLzaPe49p32aZ7SPFDhvgJV-cOpskQ5VYu&index=7 I thought, at KINTO Technologies, I might be able to get involved with autonomous driving technology. Someday, I want to work on projects related to autonomous driving. This is why I decided to work at a mobility company. I want to leverage the accessibility knowledge I've built over the years to contribute to mobility for visually impaired people. What I Aim to Achieve at KINTO Technologies Even if we aim for a future where autonomous vehicles become commonplace for visually impaired people, it won't happen immediately. For most people, visually impaired person and car are probably the most unlikely combination. As I mentioned earlier, you can probably imagine a visually impaired person being driven somewhere by someone else, but it's harder to imagine them getting into a car alone and traveling to their destination, right? However, this need definitely exists, and especially visually impaired people living in rural areas dream of a future where they can go wherever they want, whenever they want, by themselves. While public transportation can get you close to your destination, finding and navigating to the actual location from there is not easy. For example, even if you use pedestrian GPS navigation to travel, as you approach your destination, the navigation ends with a message like "You are approaching your destination." Those few meters from there can be a huge hurdle for visually impaired people who cannot see. In this way, if we replace "car" with "mobility" in the context of visually impaired individuals, everyone can easily understand that the freedom to get around is essential for achieving one's goals. To create a future where autonomous vehicles become a key means of mobility, I will work on the following. 1. Making Accessibility the Norm in the Organization In the mobility industry, the importance of accessibility is not yet fully recognized. I will engage in careful dialogue within the company to explore together: Why accessibility is necessary What teams should start with What goals we should aim for By working alongside everyone and showing them the needs of users who may not have been considered before, what inconveniences they experience, and how they solve problems, my goal for the first year is to build an organization where people don't see accessibility as someone else's problem. As a result, I hope that the accessibility of various initiatives undertaken by KINTO Technologies will improve, reach new users, eventually become a value for the organization, and lead to efforts that change society. 2. Creating an Organization Where People with Disabilities Want to Continue Working Companies with 50 or more employees are required to hire people with disabilities, but in reality, some say, I don't know how to interact with them, or I can't imagine what kind of work to assign them. There are many cases where employees are only given standardized tasks and become isolated within the company. I am not the only employee with a disability at KINTO Technologies. My role is to demonstrate how people with disabilities contribute to the organization, and to foster an environment where we can work together as colleagues toward our company's goals. And I want to create an organization where we can truly say, "I want to keep working here." I will maximize the value that can only be changed from inside the organization and contribute to the growth of KINTO Technologies. Conclusion For visually impaired people, freedom of movement has the power to change lives. At KINTO Technologies, I will take on the challenge of bringing that future closer to reality. If anyone resonates with this initiative, I would love to think about the future of mobility together.
アバター
こんにちは! 人事企画G 労務・総務チームのつんつんです。 構想から約1年。何もないガランとした空間だった場所が、ついに「働きたくなるオフィス」へと生まれ変わりました。 今回は、写真や動画だけでは伝えきれない、私たちの「こだわり」と「熱量」を詰め込んだ福岡の新オフィスツアーへご案内します。壁紙一枚、椅子一脚にまでストーリーがあるんですよ。 1. 幸せが溢れてくるエントランス まず皆様をお迎えするのは、ガラス張りの視界が良い緑豊かなエントランスです。 ただ鉢植えを置くだけでは面白くないですよね。そこで、あえて段差をつけて植物を配置し、空間に「枠」を作りました。 こうすることで、外からも中からも緑が映え、空間がキュッと締まって見えるんです。まさに「一石二鳥」のアイデア。 「幸せが溢れてくる入り口」 ――手前味噌ですが、そんな表現がぴったりの空間になりました。 2. 大人心をくすぐる「港」のラウンジとカウンター 福岡といえば「港」。ラウンジエリアのテーマはずばり「Port(港)」です。 ここで絶対に見てほしいのが、壁面のデザイン。これ、住宅の外壁などに使われる**「ガルバリウム」**という本物の建材を使っているんです。 実はこれ、ただの飾りじゃありません。このコンテナロックはガラガラと動かすことができるんです。 またカウンター奥には ハンドドリップでコーヒーを淹れたり、業務終了後にはバーカウンターのように使ってイベントを行ったりすることも想定しています。 裏 表 また休憩エリアの照明には船舶風のマリンランプや、ガラスの浮き球を模した照明を吊るし、船内のような温かみのある雰囲気を演出しています。細部まで「港」を感じさせる、大人心をくすぐる空間です。 3. 「WE BUILD BRIDGES」:絶景のラウンジエリア 窓際は、このオフィス一番の特等席です。 目の前には天神の街と橋が広がり、開放感は抜群。この壁には大きく 「WE BUILD BRIDGES」 のアートワークを施しました。福岡で有名な荒津大橋からヒントを得て拠点長が各拠点がつながるようにという想いをこめて社内のクリエイティブが作成しました。 また港方面には椅子を置かず、「スタンディングエリア」にしています。 海を眺めながら仕事するとアイデアが浮かんでくるかもですね。 夕方 夜 夜は夜景が綺麗でしてまた違った雰囲気を出しております〜 ふと視線を上げれば、窓の外に荒津大橋が見えます。(昼間の写真) 煮詰まった時にここに来て、外を眺めながら立って議論すれば、新しいアイデアの架け橋がかかるかもしれません。もちろん、電源も完備しているので集中作業もバッチリですよ。 4. 会議室エリア 執務・会議室エリアは、雰囲気をガラッと変えて大阪オフィスとはまた違う、落ち着いた大人の雰囲気です。 会議室の番号フォント、少し変わっていると思いませんか? 実はこれ、ステンシルフォントのデザインフォントを採用しているんです。IT企業らしい遊び心をこっそり忍ばせています。 会議室4つの内3つはあえて「キャスター(タイヤ)がない」モデルを選びました。 不便そうですか? いえいえ、キャスターがないと席を立った時に椅子が散らばらず、元に戻そうと心理が働き常に整然と元の位置に戻るんです。美しい空間を保つための、私たちの美学であり、こだわりポイントです。 また青色の壁はOsaka Tech Labと同じ青を使うことにより、西日本の一体感を創出してます。 5. 見えない場所にこそ「愛」を 最後に、こっそりお見せするのが、特注のベンチソファの下です。 座面をパカッと開けると、そこには災害用備蓄品が入っています。 おしゃれなデザインの裏側に、社員の安全を守る備えもしっかり隠しています。「何かあった時でも、ここなら安心」と思える場所にしたかったんです。 おわりに:こだわり抜いた「柱」の話 実はこのオフィスを作る際、柱のタイル装飾だけに予算をかけるか非常に悩みましたそれでも、「空間の空気感を変えるためには必要」と感じました。タイルを貼らなくても場所としては成立しますが、気持ちよく仕事をしてほしい・・・私たちのそんな想いが詰まっています。また本棚と一緒にすることによりスペースを有効活用しました。 コーヒーの香り、コンテナ扉の重厚感、そして窓からの素晴らしい眺め。 ぜひ一度、遊びに来てください。このこだわりの場所で、皆様とお会いできるのを楽しみにしています! 今後もオフィスの改善活動を続けていきますので、Tech Blogでの報告を楽しみにしていてくださいね。
アバター
この記事は KINTOテクノロジーズ Advent Calendar 2025 の8日目の記事です はじめに:「AIなら一瞬でした!」…で、そのまま提出していませんか? ※本記事の内容は 2025年12月時点 の情報に基づいています。各サービスの仕様・規約は変更される可能性があるため、最新情報は公式サイトをご確認ください。 「企画書のイメージ画像、AIで作ってみました」「ブログのアイキャッチ、AIなら一瞬でした」 こんなフレーズを、最近あちこちで見かけるようになりました。実際、画像生成AIはビジネスパーソンにとってかなり強力な味方です。 ただ、正直に言うと、「なんとなく便利だから使っているだけ」で終わってしまっているケースも多いのではないでしょうか。 とりあえずAIにお願いして出てきたものを、そのまま資料に貼る なんとなく不安はあるけれど、深く考える時間もない 「みんな使ってるし…まあ大丈夫でしょ」と自分を納得させる 私自身も、最初は完全にこのモードでした。 ですが、仕事で使う以上、「どこにリスクがありそうか」だけでもざっくり知っておくと、仕事の質が一段上がる感覚があります。 本記事では、「なんとなく使っている」状態から、「企業で働く一人として、責任を持って使いこなす」状態へアップデートしていくための実務的なポイントを、できるだけ現場目線で共有していけたらと思っています。 本記事の流れ 「便利さ」の裏にある、3つのモヤモヤを整理する ビジネスパーソンにおすすめの「3つのAIツール」との付き合い方 「組織のルール」より前にできる、個人としての3つの工夫 「便利さ」の裏にある、3つのモヤモヤを整理する まずは、「画像生成AIを使うときに、なんとなくモヤッとしているけど言語化できていない不安」を整理してみます。 画像生成AIのリスクは、大きく分けると次の 3つのカテゴリー に置き換えられます。 法的リスク :この画像って「誰のもの」なんだっけ? ブランドリスク :「AIだから安全」ではない オペレーションリスク :「なんか不安だけど、聞ける人がいない」 順番に見ていきます。 1. 法的リスク:この画像って「誰のもの」なんだっけ? AIで画像を作ったとき、ふと頭をよぎる疑問があります。「この画像の著作権って、誰にあるんだろう?」「自分の作品として発表していいのか?」「クライアント案件で使っても大丈夫なのか?」——そんなことを考えたことはないでしょうか。 実際のところ、使用しているツール、どの国・地域の法律が適用されるか、そしてそのツールの利用規約や自社の契約の内容、これらの組み合わせによって解釈はかなり変わってきます。 なので法律の専門家でなくても、少なくとも 「ツールごとに権利の扱いが違うらしい」 「商用利用OKかどうかは、利用規約を一度は見ておいた方がいい」 くらいの感覚を持っておくだけでも、「ちょっと立ち止まるためのブレーキ」がちゃんとかかるようになるかと思います。 そして利用規約を読むのが難しい場合や判断に迷う場合は、独断で使わずに上長、情シス部門、法務担当などに「このツール、業務で使っても大丈夫ですか?」と一度聞いてみるのも一つの手です。 それだけでも、多くのトラブルを防ぎやすくなります。 「無自覚に似てしまう」リスク さらに怖いのが、「無自覚に既存作品に似たものを作ってしまう」リスクです。 画像生成AIは膨大な画像データを学習して動いているので、こちらの意図とは関係なく、 どこかで見たことがある構図 有名キャラクターにちょっと似たもの 某ブランドっぽいロゴ といったものが、それっぽく出てきてしまうことがあります。 そのときに、「AIが勝手に作ったんで…」という言い訳は、残念ながら通用しません。 外に出すのはあくまで「自分(自社)」だからです。 「この画像、本当に大丈夫かな?」と少しでも感じたら、一度立ち止まって、権利面を確認するという習慣をつけておくと安心です。 2. ブランドリスク:「AIだから安全」ではない たとえ法的にはセーフでも、こんなケースはどうでしょうか。社内のトーン&マナーとまったく合わないビジュアルを使ってしまったり、意図せずステレオタイプな表現が混じっていたり、社会的な配慮を欠く表現になってしまっていたり——。こうしたケースは、法的には問題なくても、ブランド毀損につながりかねません。 生成AIは、学習データの傾向を反映して、思わぬ偏見を含んだ画像を出力してしまうというケースも珍しくありません。 研究レベルでも、職業・性別・人種などに関するステレオタイプを強く反映してしまうことが指摘されています。 「AIで作ったからこそ、人間がチェックする」 という意識がとても大切で、 最後は人間が「目を通す」「悩んだら誰かに見せる」こと を前提にした運用にしておくと安全度が大きく変わります。 3. オペレーションリスク:「なんか不安だけど、聞ける人がいない」 そして地味に効いてくるのが、この「運用まわり」のリスクです。 社内でAIをちゃんと使いこなしている人がまだ少ない どのツールを使っていいか、会社として決まっていない 生成した画像の保管場所がバラバラ 結局、「まあいいか」で自己判断になりがち 例えば、個人の Google アカウントや OpenAI アカウントで業務用の画像を生成していると、退職時にデータが個人側に残ってしまったり、会社側が、どのアカウントで何が作られたか把握できない、といった問題が生じる可能性があります。 可能であれば、法人プランの利用を情シスや上長に相談することをおすすめします。 プロンプトに機密情報を書き込んでしまうリスク もう一つ気にしておきたいのが、 プロンプトに機密情報を書き込んでしまうかもしれない という点です。 たとえ「入力データを学習に利用しない」ことが明示されている法人向けプランを使っていたとしても、入力した情報は一度サービス提供者のサーバーを経由します。 OpenAI や Google、Adobe なども、ヘルプやポリシーで「機密情報は入力しないでください」と明記しています。 例を挙げると、次のような情報は入力を避けるべきです。 例 入力を避けるべき内容 〇〇社向けの提案資料を作る 取引先の企業名・個人名 新製品『△△』のロゴ案を5パターン考える 未発表のプロジェクト名・製品名 売り上げの数値まとめる 社外秘の固有名詞・数値など こうした場面では、固有名詞を伏せて「大手自動車メーカーA社」「金融機関B社」、「来年発売予定の新商品」のように、 抽象化して入力する ことを心がけるとリスクを下げられます。 それでも、AIはちゃんと使えば最強の「相棒」になる ここまでリスク寄りの話が続きましたが、「じゃあ使わない方がいいのか?」というと、そうではありません。 「正しく怖がる」 ことができれば、画像生成AIは本当に頼れる相棒になると感じています。 現場目線でいうと、特にこんなメリットがあります。 イメージの共有が圧倒的に早くなる 「こんな感じの世界観で」と口頭やテキストで説明するより、AIでざっくりイメージを出してしまった方が早い場面はたくさんあります。 「素材探し」からある程度解放される ストックフォトサービスで延々とスクロールする代わりに、「夕暮れの高速道路を走る青いコンパクトカー」など、欲しいシチュエーションを直接プロンプトで指定できるのはメリットが大きいです。 アイデア出しの壁打ち相手になってくれる 「ちょっとやりすぎかも?」くらいの案を遠慮なく試せるので、思わぬ表現に出会えることもあります。 大事なのは、「魔法の箱」として丸投げするのではなく、 自分の意図を持って使う という感覚です。 AIはあくまで「相棒」であって、最終判断は自分がする。その意識があるだけで、活用の質がぐっと変わります。 ビジネスパーソンにおすすめの「3つのAIツール」との付き合い方 ここからは、現場の目線で使いやすい 3 つのツールを、「どういうときに相性がいいか」という観点で整理してみます。 # ツール 特徴 ① ChatGPT 会話ベースでイメージを固める ② Google Gemini Google Workspace連携 ③ Adobe Firefly 権利面の安心感 ① ChatGPT → 「言葉にしながらイメージを固めたい」ときに 会話ベースで「もう少し柔らかい雰囲気に」「右の人物を消して」といった修正ができるのが強みです。 企業での利用について 企業で利用する場合、個人アカウント(Free / Plus / Pro)ではなく、以下の組織向けプランが推奨されます。 ChatGPT Business (※2025年8月に「Team」から名称変更されました) ChatGPT Enterprise これらのプランでは、デフォルトで入力データが学習に使われない設定になっていると説明されています。 一方で、 Free / Plus / Pro といった個人向けプランでは、デフォルトで会話内容がモデル改善に利用される設定だと説明されています。 設定画面でオプトアウトしない限り学習に利用される可能性があるため、業務利用時は特に注意が必要です。 相性が良いシーンの例 企画の初期段階で、コンセプトの方向性を探りたいとき 「こんな感じ?」と壁打ちしながら、画像のバリエーションを試したいとき テキストと画像をセットで考えたい(タイトル案+キービジュアル案 など)とき 公式サイト(ビジネスデータのプライバシー、セキュリティ、コンプライアンス) ② Google Gemini → 「Google Workspace との連携」を重視したいときに 「スライド用の背景画像をサッと欲しい」「提案資料の中に入れるイメージをその場で作りたい」といったニーズにフィットします。 企業での利用について Google Workspace の商用プランでは、入力データや生成物はAIの学習に利用されません(商用データ保護が適用されます)。 一方で、 個人アカウント から Gemini アプリを使う場合は、会話内容が製品改善やモデル改善に利用されることがあります。 業務で使うなら、自分がどの契約・どのアカウントで Gemini を使っているかを必ず確認しておきたいところです。 相性が良いシーンの例 提案資料に差し込む、リアル寄りのイメージ画像が欲しいとき すでに Google Workspace を業務で使っているチーム ドキュメントやスライドの中で、そのままプロンプトを書いて画像を生成したいとき 公式サイト(Google Workspace の生成 AI に関するプライバシー ハブ) ③ Adobe Firefly(アドビ ファイアフライ) →「権利関係のクリーンさ」を最優先したいときに Photoshop や Illustrator でおなじみの Adobe が提供する画像生成AIです。 最大の特徴 は、Adobe が Firefly について、Adobe Stock などのライセンス済みコンテンツや著作権が消滅したパブリックドメイン画像など、 権利的にコントロールされた素材を中心に学習している と公式に明示している点です。 もちろん、これだけで「何があっても絶対安心」とは言えませんが、コンプライアンスを重視する企業のWebサイト、広告クリエイティブ、大規模なキャンペーンビジュアルなどを作るときに、ひとつの"安心材料"として選びやすいツールです。 IP補償について また、エンタープライズ向けの Firefly ソリューションやAdobe Stock の一部の生成機能では、一定の条件を満たした場合に、 生成物に対するIP補償 が提供される仕組みがあります(対象となるプランや条件は契約形態によって異なります)。 「会社でAdobeに入っているから大丈夫」と思い込まず、「 自社の契約はIP補償の対象プランか? 」を一度確認することをおすすめします。 相性が良いシーンの例 既に Photoshop / Illustrator を使っていて、その延長で生成AIを使いたいとき 権利面・ブランド面への配慮が特に重要なプロジェクト 生成画像に Content Credentials(生成経路の情報)を付けて管理したいとき 公式サイト(包括的で安全に商用利用できるAIを活用したビジネス用コンテンツ制作) 公式サイト(Adobe Fireflyによる生成AIへのアプローチ) 「組織のルール」より前にできる、個人としての3つの工夫 「うちの会社、まだAIのルールとか全然決まってないんだよね…」という方も多いと思います。 まずは、社内にすでにルールやガイドラインがないかを確認してみてください。 すでにある場合は、当然そちらが最優先です。 「確認したけど特にない」「これから整備される予定」という状況であれば、今日からできる小さな工夫を3つだけ挙げておきます。 工夫1:使うツールの「利用規約をまず確認してみる」 細かいところまで読み込めなくても、少なくとも、次のようなポイントは一度チェックしておくと、いざという時に助かります。 ^1 確認すべきポイント 商用利用はOKか 再配布・譲渡はどこまで許されているか クレジット表記が必要かどうか 「AI生成です」と書く必要があるのか この画像は「自分のもの」として扱っていいか ^2 権利がユーザーに帰属するのか それとも、サービス側からライセンスを付与される形なのか ツールによって、 「入力と出力はユーザーに帰属します」 「ユーザーに一定のライセンスを付与します」 といった表現が分かれますし、OpenAI や Adobe のように、ビジネス向けサービスでIP補償を用意しているケースもあります。 ポイント 「なんとなく大丈夫そう」ではなく、最低限、上の項目について 「どこに何が書いてあるか」だけでも見つけておく と、自分では気づかないリスクがグッと減ります。 工夫2:「これはアウトかも?」と思ったら、一度人に見せる 有名キャラに似ていないか ブランドロゴっぽい要素が入っていないか 社会的な配慮を欠く表現になっていないか など、自分だけの判断では不安なときは、チームメンバー、デザイナー、上長などに「どう思う?」と一度聞いてみることが必要だと思います。自分では気づきにくいグレーゾーンを別の人があっさり見抜いてくれる、ということもよくあります。 工夫3:プロンプトと生成物を「メモしておく」 「この画像どうやって作ったんだっけ?」という振り返りのためだけでなく、万が一、第三者から「この画像、似ていませんか?」と問い合わせが来たときに、「このツールで、このプロンプトで生成しました」と説明できる状態にしておくことが、自分や会社を守ることにつながります。 完璧な管理まではしなくても、以下の内容を記録しておくと説明しやすくなります。 記録レベル 内容 最低限 どのツールを使ったか(ChatGPT、Gemini、Firefly など)、いつ生成したか(日付) 推奨 プロンプトの全文、生成した画像のファイル名と保存場所、使用目的(社内資料 / クライアント提案 / Webサイト など) スマホのメモアプリや、生成した画像と同じフォルダにテキストファイルを置いておくだけでも、何もしないよりはるかに役立ちます。 EU AI Act や G7広島プロセス など、生成AIの透明性や説明責任が重視されつつあります。 「どのツールで、どんな指示を出して、どの画像を使ったか」をざっくりでも追えるようにしておくことは、こうした流れに備える意味でも有効です。 おわりに:「なんとなく」から「意識して使う」へ 画像生成AIは、使いこなせば本当に頼れる相棒になります。 でも、「便利だから」とただ流されるのではなく、リスクを意識しながら使うことで、仕事の質も、周囲からの信頼も、一段上がるはずです。 完璧なルールが整うのを待つ必要はありません。 利用規約を見る 迷ったら誰かに聞く 使ったツールやプロンプトを軽く記録しておく こうした小さな実践の積み重ねが、 「なんとなく使っている人」と「 責任を持って使いこなしている人 」の差になっていくのだと思います。 免責事項 本記事は、画像生成AIに関する一般的な情報の共有を目的としたものであり、 法律的な助言を行うものではありません。 具体的な判断が必要なケースでは、 各サービスの最新の利用規約や FAQ 所属組織のルール・ガイドライン 必要に応じて専門家(法務・弁護士等) に相談することをおすすめします。 ただし、ここが少しややこしいところです。 「権利はユーザーに帰属」と書かれているサービスもあれば、「ユーザーにライセンスを付与」といった書き方のサービスもあります。 特にクライアントとの契約で「著作権の譲渡」が条件になっている場合は、自己判断せず、利用規約の該当部分を法務や上長に見せて「この条件で問題ないか」を確認することをおすすめします。
アバター
この記事は KINTOテクノロジーズ Advent Calendar 2025 の8日目の記事です🎅🎄 はじめに こんにちは。ご覧いただきありがとうございます! KINTO FACTORY にて、フロントエンド開発している中本です。 今回は、技術寄りのお話ではなく今年8月に、息子が誕生した際に取得した育児休暇について、現場レベルでの引き継ぎや復帰してからの感想など、紹介させていただきます。 上司への相談 今年の4月より、FACTORY開発グループ内のフロントエンドチームのチームリーダーにアサイン頂いたので、最初は長期間で休むことに少し抵抗も感じましたが、マネージャーへ相談した際には快く育児休暇の取得を後押し頂きました。 特に産まれてからの最初の数カ月は、奥さんも心身ともに大変な時期になると思うので、なるべくサポートしてあげてください、と前向きな言葉をかけて頂きました。 そこで、ひとまず2ヶ月ほどの育児休暇を申請し、1ヶ月経った頃に復帰できそうかどうかを判断させていただく、というやり方にさせて頂きました。 引き継ぎ事項 さて、日々の業務を引き継ぎしていくにあたり、普段やっているフロントエンド開発に加え、チームリーダーとして、 新規案件のアーキテクチャ確認・方向性決定・関係部署との連携 各メンバーとの 1-on-1 各メンバーと半期ごとの振り返りと評価 採用活動 などがありました。 一つずつ、どのようにしていったか深堀ります。 新規案件のアーキテクチャ確認・方向性決定・関係部署との連携 休暇取得が始まりそうな、8月後半〜にかけてのロードマップをPdMやマネージャーと日々確認したところ、そこまで大きな新規案件は無さそうで、現在進行中のものが大半でした。 そこで、今まで自分の方で担当していた案件は、極力 Confluence の方にもまとめることを始め、「誰とどのような話をしてここまで決まっている」や「自分の中でここまではやった」などを簡単にまとめるクセを付けておきました。 各メンバーとの 1-on-1 各メンバーと半期ごとの振り返りと評価 次に、マネジメントエリアです。 チームリーダーの1つのミッションとして、半期ごとに各メンバーの振り返りを確認し評価する必要があります。 その振り返り期間が、ちょうど休暇を取得する期間とモロ被りしそうだったので、あらかじめ4月の期が始まったタイミングから、各自に目標を設定してもらい振り返りをスムーズにできるようにしておきました。 実際には、各メンバーが決めた目標と1ヶ月ごとに「何を達成した、来月は何をする」をConfluence へ記入してもらい、1-on-1の話のネタにしておりました。 自分の方でも、気付いた部分・頑張ってくれた点は上記 Confluence へメモしておき、振り返りの際にフィードバックできるようにしておきました。 産まれるタイミングで休暇を取得する予定だったため、もし評価期間と被ってしまった場合は、上記 Confluence をそのままマネージャーへお渡しすれば、各メンバーの目標と成果をすぐ確認できると考えておりました。 採用活動 こちらは、基本的にマネージャーへ代わりをお願いさせて頂きました。 ただ、開発メンバーにも面接時から応募者の技術力やコミュニケーション力などを感じて欲しいので、面接に同行してもらい、現場レベルでのコミュニケーションやテックスキルなどを確認してもらいました。 おやすみ中 さて、そのように大体の引き継ぎ目処が経った8月24日、無事に元気な男の子が産まれました! そこから3日間の特別休暇を頂き、役所関係や病院との往復を繰り返し、育児休暇開始前日に会社からの貸与品(PC、スマホ、社員証)などを返却しに1日だけ出社しました。 ここから約2ヶ月弱の間、業務からは一歩引いて、育児にフルコミットすることとします! 育児は大変 FACTORY開発グループには、現役の子育てパパがいっぱいいるので、色々なお話を産前から伺ってましたが、例にも漏れずやはり最初の1ヶ月は大変でした... 我が家は、妻と自分で半日ずつの交代制で育児と睡眠を分け、自分は朝起きてから夜寝るまでの育児を担当しました。妻が、そのほうが育児休暇明けも睡眠サイクルを崩さず復帰できるのでは?と言ってくれたので、基本的に日中は起きて・夜間に就寝できるようになりました。 ミルク・おむつ替え・あやす・沐浴など、やることが分単位でやってくるので、自分のことを考える余裕もなく、最初の1-2週間は業務のことは全く頭になかったかと思います (´ω`;) でもサービスも気になる 休暇に入って、1ヶ月ほど経ったくらいで、マネージャーとお話しでき、案件の進捗状況やチームの状態など共有いただくことができました。1ヶ月間、外出も最低限で、他人とお話するのも久しぶりだったので、精神的にもすごく解放された感じがしました。 また、この頃からだいぶ育児にも慣れて来ており、息子も落ち着いて寝る時間が増えてきたので、ちょこちょこサービスの更新状況を見に行っていた記憶があります。 KINTO FACTORYは水曜日に新商品の発売開始や、新機能の提供が始まるので、水曜日はサイトに訪れ「今月は何がでたかな?」と確認していました。 帰還 そしてこの度、65日間の育児休暇を終え11月より現場に復帰しました。 グループのメンバーなどにも大きな変化はなく、進行中だった案件が後ろ倒しになっていたりと、思っていたより休暇に入る前からそこまで大幅な変化もなく、すんなりと復帰することができた印象です。 ただ、いきなり妻が日中帯でのワンオペ育児となるので、なるべく負荷を低減してもらうために、できるだけ朝早い時間に出社し夕方もなるべく早く帰宅するように心がけています。 このあたりも、フルフレックス制度によりMTGの調整やチーム内での合意を取れれば、自由に稼働時間を調整することが可能となっております。 さいごに 最初の2ヶ月間育児に専念することで、息子が初めて見せた笑顔だったり、あーうーと唸りだしたりと、日に日に成長する姿を間近に見ることができ、この時期に育児に関わることで貴重な体験をすることができたと思います。 このような体験ができたのも、育児休暇を取得するにあたって、快く送り出して頂いたマネージャー、そしてグループの皆さんにこの場を借りて感謝させて頂きたいです。 KINTO テクノロジーズでは、このように男性でも育児休暇を取得しやすい環境かと思います。実際に、今年だけでも他部署含め多くの男性エンジニアが育児休暇を取得しているお話を聞いています(社内に息子の同級生も多い!)。 育児休暇を取得する準備や、復帰について少しでも参考になれば幸いです。 また、下記記事では同じFACTORY開発グループ内の先輩パパの1日が紹介されていますので、そちらもぜひご覧頂ければと思います! パパママエンジニア必見!KTCパパエンジニアの1日
アバター
This article is for Day 8 of the KINTO Technologies Advent Calendar 2025 . I'm Tsuyoshi Yamada , an Android engineer at KINTO Technologies. In this article, I'll introduce a collection of examples for adding animations to Jetpack Compose UIs with minimal code additions and changes to enhance the impression of animations. 1. Introduction Animation is one of the key elements that significantly affects the impression of a smartphone app. Well-placed, thoughtful animations provide visual feedback for user actions, make app behavior easier to understand, enhance the app's impression, and increase user trust in quality. Jetpack Compose leverages the characteristics of declarative UI to enable highly productive animation implementation with shorter, more concise code than the traditional View system. This article introduces practical techniques for easily adding animations to existing source code with minimal additions and modifications. This article verifies source code using Jetpack Compose Libraries BOM 2025.12.00, which includes the latest stable version 1.10.0 of Compose Animation at the time of writing. 2. Animation for UI Components with Coordinate Specifications The following code implements a simple puzzle game (15 Puzzle) in concise code: @Composable fun Puzzle15(modifier: Modifier = Modifier) { var puzzleState by remember { mutableStateOf(PuzzleState.generate()) } var moves by remember { mutableIntStateOf(puzzleState.moves) } val solved = puzzleState.isSolved() val titleStyle = MaterialTheme.typography.headlineLarge.merge(fontWeight = FontWeight.W600) val movesStyle = MaterialTheme.typography.titleMedium val solvedStyle = MaterialTheme.typography.titleLarge.merge(color = Color.Green, fontWeight = FontWeight.W600) val buttonStyle = MaterialTheme.typography.titleMedium.merge(fontWeight = FontWeight.W600) Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Column( modifier = modifier .fillMaxSize() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text( text = "15 Puzzle", style = titleStyle, modifier = Modifier.padding(bottom = 16.dp) ) Text(text = "Moves: $moves", style = movesStyle) PuzzleGrid( puzzleState = puzzleState, modifier = Modifier.padding(vertical = 24.dp) ) { index -> if (solved) return@PuzzleGrid puzzleState.moveTile(index) moves = puzzleState.moves } Button(onClick = { puzzleState = PuzzleState.generate() moves = 0 }) { Text("New Game", style = buttonStyle) } } if (solved) { Text(text = "🎉 Solved! 🎉", style = solvedStyle) } } } The 15 tiles drawn in a 4x4 grid are displayed with the following code: @Composable private fun PuzzleGrid( puzzleState: PuzzleState, modifier: Modifier = Modifier, onTileClick: (Int) -> Unit ) { BoxWithConstraints( modifier = modifier .fillMaxWidth() .aspectRatio(1F) ) { val gridSize = maxWidth val tileSize = (gridSize - 12.dp) / 4 // 3 gaps of 4dp for (position in 0.until(PuzzleState.GRID_COUNT)) { val value = puzzleState.tileAt(position) if (value == 0) continue val targetOffset = DpOffset( x = (tileSize + 4.dp) * (position % 4), y = (tileSize + 4.dp) * (position / 4) ) PuzzleTile( value = value, onClick = { onTileClick(position) }, modifier = Modifier .offset(x = targetOffset.x, y = targetOffset.y) .size(tileSize) ) } } } Users tap movable tiles to move them to empty squares, repeating this operation to complete the puzzle. Up to 3 tiles can be moved simultaneously in a single move. While this is enjoyable enough as a simple game, it would be nice to have the physical sensation of moving tiles like in a real-world puzzle game. We aim to express this through animation. The class representing the logical puzzle state is as follows: class PuzzleState private constructor(private val tiles: IntArray) { var moves = 0 private set fun isSolved(): Boolean = tiles.all { tiles[it] == it + 1 || it == 15 } private fun getMoveOffsetOrZero(index: Int): Int { val emptyIndex = tiles.indexOf(0) val row = index / 4 val col = index % 4 val emptyRow = emptyIndex / 4 val emptyCol = emptyIndex % 4 return when { row == emptyRow -> { if (col < emptyCol) 1 else -1 } col == emptyCol -> { if (row < emptyRow) 4 else -4 } else -> 0 } } fun moveTile(index: Int) { val offset = getMoveOffsetOrZero(index) if (offset == 0) return var position = index do { position += offset } while (position >= 0 && position < tiles.size && tiles[position] != 0) do { val next = position - offset tiles[position] = tiles[next] position = next } while (position != index) tiles[index] = 0 moves += 1 } fun tileAt(index: Int) = tiles[index] companion object { const val GRID_COUNT = 16 fun generate(): PuzzleState { val tiles = IntArray(GRID_COUNT) { it } // Shuffle (generate only solvable configurations) do { tiles.shuffle(Random.Default) } while (!isSolvable(tiles)) return PuzzleState(tiles) } private fun isSolvable(tiles: IntArray): Boolean { val inversions = (0..15).sumOf { idx -> (idx + 1 until tiles.size).count { tiles[idx] != 0 && tiles[it] != 0 && tiles[idx] > tiles[it] } } // Solvable if blank is on odd row (from bottom) and inversions are even, or blank is on even row and inversions are odd return (3 - tiles.indexOf(0) / 4) % 2 == inversions % 2 } } } 2.1. Define a State that Holds Coordinate (Offset) State In the following code, we convert targetOffset from the original PuzzleGrid(...) using animateValueAsState(...) to define a new animatedOffset , and replace the arguments of Modifier.offset(...) in PuzzleTile(...) . animatedOffset represents the same offset as targetOffset while having the capability to smoothly express changes in offset from before to after tile movement. However, in this example, these 12 lines of code alone don't animate; we needed to use the variable tilePositions that sorts the display order, as commented with // Track current position of each tile (by value) . This sorting ensures that the order of tiles in the for loop within PuzzleGrid(...) is always kept in order of the numbers on the tiles. By doing this, under Jetpack Compose's declarative UI concept, we ensure expression continuity by always identifying tiles 1-15 as the same before and after movement, enabling animation display (we hoped the label argument of animateValueAsState would identify composable identity, but this effect was not confirmed in androidx.compose.animation library 1.10.0. Also, using key(...) to identify composables was considered, but this also doesn't seem to work): @Composable private fun PuzzleGrid( puzzleState: PuzzleState, modifier: Modifier = Modifier, onTileClick: (Int) -> Unit ) { // Track current position of each tile (by value) val tilePositions = IntArray(16) { -1 } repeat(PuzzleState.GRID_COUNT) { index -> tilePositions[puzzleState.tileAt(index)] = index } BoxWithConstraints( modifier = modifier .fillMaxWidth() .aspectRatio(1F) ) { val gridSize = maxWidth val tileSize = (gridSize - 12.dp) / 4 // 3 gaps of 4dp // Draw tiles except blank (for each value 1-15) for (value in 1.until(PuzzleState.GRID_COUNT)) { val position = tilePositions[value] if (position == -1) continue val targetOffset = DpOffset( x = (tileSize + 4.dp) * (position % 4), y = (tileSize + 4.dp) * (position / 4) ) val animatedOffset by animateValueAsState( targetValue = targetOffset, typeConverter = TwoWayConverter( convertToVector = { AnimationVector2D(it.x.value, it.y.value) }, convertFromVector = { DpOffset(Dp(it.v1), Dp(it.v2)) } ), animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMedium ), label = "tile_$value" ) PuzzleTile( value = value, onClick = { onTileClick(position) }, modifier = Modifier .offset(x = animatedOffset.x, y = animatedOffset.y) .size(tileSize) ) } } } Nonetheless, with the addition of about a dozen lines of code and a few changes, we were able to express animation while keeping the code structure mostly intact. To customize this animation, try changing the typeConverter , animationSpec , and other arguments of animateValueAsState(...) . I'll continue introducing techniques for efficiently adding animations in the following sections. ![パズルのアニメーション中](/assets/blog/authors/tsuyoshi_yamada/tech-blog_compose-animation_puzzle.webp =256x) Fig. 1: Puzzle animating 3. Adding Animation for Show/Hide Transitions In the puzzle game above, the display when solving the puzzle is rather plain, so we'd like to add some flair. That said, since it's a mini-game, something modest will do. For now, let's consider an effect where something pops out when the puzzle is solved. There's a simple method prepared for this. 3.1. Wrap with AnimatedVisibility(...) and Add Modifier.animateEnterExit(...) Inside Simply changing if (solved) { ... } to AnimatedVisibility (solved) { ... } enables default animations when transitioning from hidden to visible and from visible to hidden. To change the default animation, add animateEnterExit(...) to the Modifier of each composable wrapped by AnimatedVisibility(solved) { ... } : @Composable fun Puzzle15(modifier: Modifier = Modifier) { // ... Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { // ... AnimatedVisibility(solved) { Text( text = "🎉 Solved! 🎉", modifier = Modifier.animateEnterExit( enter = scaleIn( animationSpec = spring( dampingRatio = Spring.DampingRatioHighBouncy, stiffness = Spring.StiffnessMedium ) ), exit = None ), style = solvedStyle ) } } } The enter and exit of Modifier.animateEnterExit(...) set the animations for transitioning from hidden to visible and from visible to hidden, respectively. The default values are set to fadeIn() and fadeOut() , respectively. In this case, since we only want to set animation when appearing, we set None for exit so it disappears without animation. Wrapping a composable with an upper-level composable to create a scope ( AnimatedVisibilityScope ) and adding individual settings to the Modifier of wrapped individual composables is a frequently used technique in Jetpack Compose. Similar techniques will appear later. 4. Expressing Animation Where Old Screen Disappears and New Screen Appears on Screen Update The following code represents the action of moving on a 2D integer coordinate plane by tapping arrow buttons for up, down, left, and right: @Composable fun FlatField(modifier: Modifier = Modifier) { var xy by remember { mutableStateOf(Coordinates2D(0, 0)) } Box( modifier = Modifier .fillMaxSize() .background(xy.background) .safeContentPadding() ) { IconButton( modifier = modifier.align(Alignment.CenterStart), onClick = { xy = xy.goLeft() } ) { Icon( modifier = Modifier.size(64.dp), imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "goLeft", tint = xy.foreground ) } IconButton( modifier = Modifier.align(Alignment.TopCenter), onClick = { xy = xy.goUp() } ) { Icon( modifier = Modifier .size(64.dp) .rotate(90F), imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "goUp", tint = xy.foreground ) } IconButton( modifier = Modifier.align(Alignment.CenterEnd), onClick = { xy = xy.goRight() } ) { Icon( modifier = Modifier.size(64.dp), imageVector = Icons.AutoMirrored.Filled.ArrowForward, contentDescription = "goRight", tint = xy.foreground ) } IconButton( modifier = Modifier.align(Alignment.BottomCenter), onClick = { xy = xy.goDown() } ) { Icon( modifier = Modifier .size(64.dp) .rotate(90F), imageVector = Icons.AutoMirrored.Filled.ArrowForward, contentDescription = "goDown", tint = xy.foreground ) } Text( text = xy.coordinateString, modifier = Modifier.align(Alignment.Center), color = xy.foreground, fontSize = 64.sp ) } } You can repeat operations like moving from the initial white location to the red location via the down button, then to the blue location via the right button, and so on, but without motion, it's difficult to grasp the sense of movement. This is exactly when adding animation is effective. The class representing the state during movement is as follows: data class Coordinates2D(val x: Int, val y: Int) { val background: Color val foreground: Color val coordinateString: String init { val index = nonNegativeRemainder() background = backgroundColors[index] foreground = foregroundColors[index] coordinateString = coordinateString(x, y) } private fun nonNegativeRemainder(): Int = ((x + y) % 3).let { if (it < 0) it + 3 else it } fun goLeft() = Coordinates2D(x - 1, y) fun goUp() = Coordinates2D(x, y - 1) fun goRight() = Coordinates2D(x + 1, y) fun goDown() = Coordinates2D(x, y + 1) companion object Companion { private val backgroundColors = arrayOf(Color.White, Color(0xED, 0x29, 0x39), Color(0x00, 0x23, 0x95)) private val foregroundColors = arrayOf(Color.Black, Color.White, Color.White) private fun coordinateString(x: Int, y: Int) = if (x == 0 && y == 0) "O" else "(${x}, ${y})" } } 4.1. Wrap with AnimatedContent(...) and Add Animation for Transitioning from Old Screen to New Screen The following wraps the Box(...) inside FlatField(Modifier) with AnimatedContent(...) , but since the animation definition requires information about the size of Box(...) , we change Box(...) to BoxWithConstraints(...) , reference constraints , and use the values of maxWidth and maxHeight as arguments to AnimatedContent(...) : @Composable fun FlatField(modifier: Modifier = Modifier) { var xy by remember { mutableStateOf(Coordinates2D(0, 0)) } var width by remember { mutableIntStateOf(0) } var height by remember { mutableIntStateOf(0) } AnimatedContent( modifier = modifier.fillMaxSize(), targetState = xy, transitionSpec = { val deltaX = targetState.x - initialState.x val deltaY = targetState.y - initialState.y slideIn { IntOffset(x = deltaX * width, y = deltaY * height) } togetherWith slideOut { IntOffset(x = -deltaX * width, y = -deltaY * height) } }, label = "coordinates2D" ) { targetXy -> BoxWithConstraints( modifier = Modifier .fillMaxSize() .background(targetXy.background) .safeContentPadding() ) { width = constraints.maxWidth height = constraints.maxHeight IconButton( modifier = Modifier.align(Alignment.CenterStart), onClick = { xy = targetXy.goLeft() } ) { Icon( modifier = Modifier.size(64.dp), imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "goLeft", tint = targetXy.foreground ) } IconButton( modifier = Modifier.align(Alignment.TopCenter), onClick = { xy = targetXy.goUp() } ) { Icon( modifier = Modifier .size(64.dp) .rotate(90F), imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "goUp", tint = targetXy.foreground ) } IconButton( modifier = Modifier.align(Alignment.CenterEnd), onClick = { xy = targetXy.goRight() } ) { Icon( modifier = Modifier.size(64.dp), imageVector = Icons.AutoMirrored.Filled.ArrowForward, contentDescription = "goRight", tint = targetXy.foreground ) } IconButton( modifier = Modifier.align(Alignment.BottomCenter), onClick = { xy = targetXy.goDown() } ) { Icon( modifier = Modifier .size(64.dp) .rotate(90F), imageVector = Icons.AutoMirrored.Filled.ArrowForward, contentDescription = "goDown", tint = targetXy.foreground ) } Text( text = targetXy.coordinateString, modifier = Modifier.align(Alignment.Center), color = targetXy.foreground, fontSize = 64.sp ) } } } In the transitionSpec argument of AnimatedContent(...) , we define the animation by merging slideOut { ... } that pushes out the old screen and slideIn { ... } that brings in the new screen using infix EnterTransition.togetherWith(ExitTransition) . The argument ( targetXy ) of the lambda expression given to the content argument of AnimatedContent(...) holds the state of the screen being pushed out, and the state is updated by assigning the new state obtained from targetXy to xy in the onClick of each IconButton(...) . ![アニメーションによる疑似スクロール](/assets/blog/authors/tsuyoshi_yamada/tech-blog_compose-animation_pseudo-scroll_1.webp =256x) ![アニメーションによる疑似スクロール](/assets/blog/authors/tsuyoshi_yamada/tech-blog_compose-animation_pseudo-scroll_2.webp =256x) Fig. 2-1, 2-2: Pseudo-scroll by animation With this code, if you (forcibly build while ignoring compiler warnings by) writing xy = xy.goLeft() instead of using targetXy , the old screen being pushed out during animation won't display correctly. This writing style shows that AnimatedContent(...) correctly manages the state during animation. To customize this animation, try changing the transitionSpec argument of AnimatedContent(...) , etc. Adding animation makes it much easier to grasp the sense of position movement. Looking at this motion, it seems this could also be used as a two-dimensional pager. Jetpack Compose provides HorizontalPager for horizontal scrolling and VerticalPager for vertical scrolling, but there's no standard two-dimensional pager. However, you can create a pager-like UI just by adding animation. Of course, since we've only added animation, you can't swipe to see part of an adjacent page, but with just about a dozen lines of additions and some changes, you can significantly change the app's impression. 5. Animation Connecting Before and After Screen Transitions in NavHost(...) Starting from Compose Animation version 1.10.0 , SharedTransitionLayout became stable. This defines shared elements for screens created with composables. What are shared elements? Check the short video in Shared element transitions in Compose . Here's a code example when using NavHost(...) : private val colorMap = mapOf( "赤" to Color.Red, "緑" to Color.Green, "青" to Color.Blue, "シアン" to Color.Cyan, "マゼンタ" to Color.Magenta, "黄" to Color.Yellow, "茶" to Color(132, 74, 43), "群青" to Color(76, 108, 179), "カーキー" to Color(197, 160, 90) ) @Composable fun GridTransform(modifier: Modifier = Modifier) { val navController = rememberNavController() NavHost( modifier = modifier .safeContentPadding() .fillMaxSize(), navController = navController, startDestination = ROUTE_SMALL_SQUARE ) { composable(route = ROUTE_SMALL_SQUARE) { val onClick: (String) -> Unit = { navController.navigate("$ROUTE_LARGE_SQUARE?$ARG_SHARED=$it") } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Column( modifier = Modifier .aspectRatio(1f) .padding(8.dp) .fillMaxSize() .background( color = MaterialTheme.colorScheme.primaryContainer, shape = RoundedCornerShape(16.dp) ) ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( "赤", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "緑", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "青", modifier = Modifier.weight(1f), onClick = onClick ) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( "シアン", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "マゼンタ", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "黄", modifier = Modifier.weight(1f), onClick = onClick ) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( "茶", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "群青", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "カーキー", modifier = Modifier.weight(1f), onClick = onClick ) } } } } composable( route = "$ROUTE_LARGE_SQUARE?$ARG_SHARED={sharedKey}", arguments = listOf(navArgument("sharedKey") { type = NavType.StringType }) ) { entry -> val colorName = entry.arguments?.getString("sharedKey") ?: "" LargeSquare(colorName) { navController.popBackStack() } } } } @Composable fun ColorButton( colorName: String, modifier: Modifier = Modifier, onClick: (String) -> Unit ) { TextButton( modifier = modifier .padding(16.dp) .aspectRatio(1f) .background(color = colorMap[colorName]!!, shape = RoundedCornerShape(16.dp)), onClick = { onClick(colorName) } ) { Text(colorName, color = Color.White) } } @Composable private fun LargeSquare( colorName: String, modifier: Modifier = Modifier, onBack: () -> Unit ) { Box( modifier = modifier .padding(16.dp) .fillMaxSize() .aspectRatio(1f) .background(color = colorMap[colorName]!!, shape = RoundedCornerShape(16.dp)) .clickable { onBack() } ) { Text(text = colorName, modifier = Modifier.padding(8.dp), color = Color.White) } } Note: This code omits countermeasures against rapid tapping of the back button and 9-color buttons for simplicity. Please test while avoiding rapid tapping in short intervals. On the initial screen, 9 color buttons are displayed, and tapping a button shows a large square in the tapped button's color. When the large square is displayed, pressing the back button (or performing a back gesture) returns to the 9-color button screen. Here, you can see fade-in and fade-out animations when returning to the large square screen by button tap and when returning to the 9-color button screen by back button. This is specified by the default values of the enterTransition , exitTransition , popEnterTransition , popExitTransition , sizeTransform arguments of NavHost(...) . You can override this setting with animation that associates shared elements before and after specific screen transitions by adding code and making modifications as shown below for specific screen transitions. For implementing shared element animation in screen transitions that don't involve back stack changes via NavHost(...) , refer to Shared element transitions in Compose . During screen transitions from left to right and right to left in the table below, the animation visually expresses that the square on the right screen that appeared from the button tapped on the left screen (bottom right) is exactly the target content. 9-color button screen Large square screen ![9色のボタンの画面](/assets/blog/authors/tsuyoshi_yamada/tech-blog_compose-animation_9-buttons.webp =256x) Fig. 3-1 ![Large square screen](/assets/blog/authors/tsuyoshi_yamada/tech-blog_compose-animation_large-square.webp =256x) Fig. 3-2 5.1. Wrap with SharedTransitionLayout { ... } and Share Screen Elements Here we introduce the implementation of shared element animation before and after screen transitions via NavHost(...) : @Composable fun GridTransform(modifier: Modifier = Modifier) { val navController = rememberNavController() SharedTransitionLayout { NavHost( modifier = modifier .safeContentPadding() .fillMaxSize(), navController = navController, startDestination = ROUTE_SMALL_SQUARE ) { composable(route = ROUTE_SMALL_SQUARE) { val onClick: (String) -> Unit = { navController.navigate("$ROUTE_LARGE_SQUARE?$ARG_SHARED=$it") } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Column( modifier = Modifier .aspectRatio(1f) .padding(8.dp) .fillMaxSize() .background( color = MaterialTheme.colorScheme.primaryContainer, shape = RoundedCornerShape(16.dp) ) ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( this@composable, "赤", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "緑", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "青", modifier = Modifier.weight(1f), onClick = onClick ) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( this@composable, "シアン", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "マゼンタ", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "黄", modifier = Modifier.weight(1f), onClick = onClick ) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( this@composable, "茶", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "群青", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "カーキー", modifier = Modifier.weight(1f), onClick = onClick ) } } } } composable( route = "$ROUTE_LARGE_SQUARE?$ARG_SHARED={sharedKey}", arguments = listOf(navArgument("sharedKey") { type = NavType.StringType }) ) { entry -> val colorName = entry.arguments?.getString("sharedKey") ?: "" LargeSquare(this, colorName) { navController.popBackStack() } } } } } @Composable fun SharedTransitionScope.ColorButton( animatedContentScope: AnimatedVisibilityScope, colorName: String, modifier: Modifier = Modifier, onClick: (String) -> Unit ) { TextButton( modifier = modifier .padding(16.dp) .aspectRatio(1f) .background(color = colorMap[colorName]!!, shape = RoundedCornerShape(16.dp)) .sharedBounds( sharedContentState = rememberSharedContentState(colorName), animatedVisibilityScope = animatedContentScope, boundsTransform = { _, _ -> tween(durationMillis = 500) }, enter = fadeIn(), exit = fadeOut(), resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds() ), onClick = { onClick(colorName) } ) { Text(colorName, color = Color.White) } } @Composable private fun SharedTransitionScope.LargeSquare( animatedContentScope: AnimatedVisibilityScope, colorName: String, modifier: Modifier = Modifier, onBack: () -> Unit ) { Box( modifier = modifier .padding(16.dp) .fillMaxSize() .aspectRatio(1f) .background(color = colorMap[colorName]!!, shape = RoundedCornerShape(16.dp)) .sharedBounds( sharedContentState = rememberSharedContentState(colorName), animatedVisibilityScope = animatedContentScope, boundsTransform = { _, _ -> tween(durationMillis = 500) }, enter = fadeIn(), exit = fadeOut(), resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds() ) .clickable { onBack() } ) { Text(text = colorName, modifier = Modifier.padding(8.dp), color = Color.White) } } Wrapping the outer layer with SharedTransitionLayout { ... } and using Modifier.sharedBounds(...) within SharedTransitionScope to associate shared elements is the same as for screen transitions that don't use NavHost(...) . For the animatedVisibilityScope argument of Modifier.sharedBounds(...) , we use AnimatedContentScope ( this@composable ) derived from NavGraphBuilder.composable(...) . This enables displaying animation between shared elements during screen transitions via NavHost(...) . In SharedTransitionScope.ColorButton(...) and SharedTransitionScope.LargeSquare(...) , verify that screen elements are shared before and after screen transitions by using the button's color name as the key argument of rememberSharedContentState(Any) . The new coding required to set up shared element animation is: Wrap with SharedTransitionLayout { ... } Set sharedBounds(...) (or sharedElement(...) ) to define shared elements and match the key between shared elements Pass the two scopes SharedTransitionScope and AnimatedVisibilityScope required for 2. to the composable These can be implemented without significantly changing the structure of the code before animation settings. If it's difficult to apply with minimal changes to existing code, try refactoring to a structure that's easier to modify. Shared element animation is beautiful when it fits well, but animation that perfectly matches the screen design image may be difficult. In that case, try adjusting the enter , exit , boundsTransform , resizeMode arguments of Modifier.sharedBounds(...) , etc. 5.2. Relationship with Predictive Back Shared element animation overrides the default screen transition animation of NavGraphBuilder.composable(...) . Additionally, when predictive back animation is enabled, that is, when android:enableOnBackInvokedCallback="true" is specified in AndroidManifest.xml for API levels 33-35, or when android:enableOnBackInvokedCallback="false" is not specified in AndroidManifest.xml for API level 36 or higher, it also overrides the back animation via NavHost(...) . Build the app with predictive back animation enabled, set the device to gesture navigation mode, and slowly perform the back gesture on the large square screen above, and you can easily confirm that the shared element animation slowly reverses. Also, on API level 36 or higher and Android OS 16 or higher, setting to button navigation mode and long-pressing the back button should show the shared element reverse animation. Thus, the back animation of NavHost(...) affects the predictive back animation settings. With this in mind, you need to decide whether to enable predictive back animation settings. As of API level 36, you can disable predictive back animation by specifying android:enableOnBackInvokedCallback="false" . 6. Conclusion This article introduced 4 examples of techniques for implementing practical animations in Jetpack Compose with minimal changes. Animation within apps is not essential functionality in most cases, so implementation tends to be omitted especially in development projects with tight schedules. But depending on usage, it has the potential to bring significant usability improvements and greatly enhance the app's impression. If there are abundant means to achieve this with minimal effort, you can easily try implementation. Many APIs in Compose Animation are designed to be declarative , meaning you can add animations with the feeling of declaring that you'll animate UI elements, making it easy to avoid the complexity of having to define behavior through detailed procedural descriptions. I hope these can be utilized to improve the quality of many apps. 7. References Android API reference Quick guide to Animations in Compose Animation modifiers and composables Add support for predictive back animations
アバター
この記事は KINTOテクノロジーズ Advent Calendar 2025 の 8 日目の記事です🎅🎄 KINTOテクノロジーズのAndroidエンジニア 山田 剛 です。 本記事では、少しのコード追加・変更でJetpack Composeを利用したUIにアニメーションを追加し、アニメーションの印象を向上させるための事例集を紹介します。 1. はじめに スマートフォンアプリの印象を大きく左右する要素の一つがアニメーションです。適所に用意された気の利いたアニメーションは、ユーザーの操作に対する視覚的なフィードバックを提供し、アプリの動作の意味を理解しやすくするとともに、アプリの印象を向上させ、品質に対するユーザーの信頼感を増します。Jetpack Composeでは、「宣言的UI」の特性を活かした、従来のViewシステムよりも短く簡潔なコードで、生産性の高いアニメーションの実装が可能になっています。本記事では、その技術の中から、小規模なコードの追加・修正で既存のソースに容易にアニメーションを追加できる実用的なテクニックを紹介します。 本記事では、執筆時点での Compose Animation の最新の安定版 1.10.0 を含んだ Jetpack Compose Libraries BOM 2025.12.00 でソースコードを検証しています。 2. 座標を指定するタイプのUI部品のアニメーション 以下は簡単なパズルゲーム(15パズル)を短いコードで書いています: @Composable fun Puzzle15(modifier: Modifier = Modifier) { var puzzleState by remember { mutableStateOf(PuzzleState.generate()) } var moves by remember { mutableIntStateOf(puzzleState.moves) } val solved = puzzleState.isSolved() val titleStyle = MaterialTheme.typography.headlineLarge.merge(fontWeight = FontWeight.W600) val movesStyle = MaterialTheme.typography.titleMedium val solvedStyle = MaterialTheme.typography.titleLarge.merge(color = Color.Green, fontWeight = FontWeight.W600) val buttonStyle = MaterialTheme.typography.titleMedium.merge(fontWeight = FontWeight.W600) Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Column( modifier = modifier .fillMaxSize() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text( text = "15 Puzzle", style = titleStyle, modifier = Modifier.padding(bottom = 16.dp) ) Text(text = "Moves: $moves", style = movesStyle) PuzzleGrid( puzzleState = puzzleState, modifier = Modifier.padding(vertical = 24.dp) ) { index -> if (solved) return@PuzzleGrid puzzleState.moveTile(index) moves = puzzleState.moves } Button(onClick = { puzzleState = PuzzleState.generate() moves = 0 }) { Text("New Game", style = buttonStyle) } } if (solved) { Text(text = "🎉 Solved! 🎉", style = solvedStyle) } } } 4x4 の升目に描く15枚のタイルは、以下のコードで表示しています: @Composable private fun PuzzleGrid( puzzleState: PuzzleState, modifier: Modifier = Modifier, onTileClick: (Int) -> Unit ) { BoxWithConstraints( modifier = modifier .fillMaxWidth() .aspectRatio(1F) ) { val gridSize = maxWidth val tileSize = (gridSize - 12.dp) / 4 // 3 gaps of 4dp for (position in 0.until(PuzzleState.GRID_COUNT)) { val value = puzzleState.tileAt(position) if (value == 0) continue val targetOffset = DpOffset( x = (tileSize + 4.dp) * (position % 4), y = (tileSize + 4.dp) * (position / 4) ) PuzzleTile( value = value, onClick = { onTileClick(position) }, modifier = Modifier .offset(x = targetOffset.x, y = targetOffset.y) .size(tileSize) ) } } } ユーザーは動かせるタイルをタップすることで空いた升目にタイルを移動させる操作を繰り返し、パズルの完成を目指します。タイルは1手で最大3枚同時に動かせます。これだけでもシンプルゲームとして充分楽しめますが、リアル世界のパズルゲームのように物理的にタイルを動かしている感触がほしいところです。それをアニメーションで表現することを目指します。 なお、論理的なパズルの状態を表すクラス PuzzleState は以下のようになっています: class PuzzleState private constructor(private val tiles: IntArray) { var moves = 0 private set fun isSolved(): Boolean = tiles.all { tiles[it] == it + 1 || it == 15 } private fun getMoveOffsetOrZero(index: Int): Int { val emptyIndex = tiles.indexOf(0) val row = index / 4 val col = index % 4 val emptyRow = emptyIndex / 4 val emptyCol = emptyIndex % 4 return when { row == emptyRow -> { if (col < emptyCol) 1 else -1 } col == emptyCol -> { if (row < emptyRow) 4 else -4 } else -> 0 } } fun moveTile(index: Int) { val offset = getMoveOffsetOrZero(index) if (offset == 0) return var position = index do { position += offset } while (position >= 0 && position < tiles.size && tiles[position] != 0) do { val next = position - offset tiles[position] = tiles[next] position = next } while (position != index) tiles[index] = 0 moves += 1 } fun tileAt(index: Int) = tiles[index] companion object { const val GRID_COUNT = 16 fun generate(): PuzzleState { val tiles = IntArray(GRID_COUNT) { it } // シャッフル(解ける配置のみ生成) do { tiles.shuffle(Random.Default) } while (!isSolvable(tiles)) return PuzzleState(tiles) } private fun isSolvable(tiles: IntArray): Boolean { val inversions = (0..15).sumOf { idx -> (idx + 1 until tiles.size).count { tiles[idx] != 0 && tiles[it] != 0 && tiles[idx] > tiles[it] } } // 空白が奇数行(下から)にあり、転置数が偶数の場合、または空白が偶数行にあり、転置数が奇数の場合は解ける return (3 - tiles.indexOf(0) / 4) % 2 == inversions % 2 } } } 2.1. 座標(オフセット)の状態を保持する State を定義する 以下のコードでは、修正前の PuzzleGrid(...) の targetOffset を animateValueAsState(...) で変換して新たに animatedOffset を定義し、 PuzzleTile(...) の Modifier.offset(...) の引数を置き換えています。 animatedOffset は targetOffset と同じオフセットを表現しつつ、タイルを移動させるときの移動前から移動後のオフセットの変化をなめらかに表現する機能を持っています。 ただし、この例の場合はこの12行のコード追加だけではアニメーションせず、 // 各タイル(値)の現在位置を追跡 とコメントされた部分の表示順のソートを行った変数 tilePositions を使う必要がありました。これは、 PuzzleGrid(...) における for ループ内でのタイルの順番を常にタイルに描かれた数字の順に保つための並べ替えを行っています。このようにすることで、Jetpack Composeの「宣言的UI」の考え方のもと、移動前と移動後の1〜15のタイルをそれぞれ常に同一視できるようにして表現の連続性を確保し、アニメーションの表示を可能にしています( animateValueAsState の label 引数によって composable の同一性を同定してほしいところでしたが、 androidx.compose.animation ライブラリ 1.10.0 ではそのような効果は確認できませんでした。また、 key(...) を用いて composable を同定させる方法も考えられますが、これもうまくいかないようです): @Composable private fun PuzzleGrid( puzzleState: PuzzleState, modifier: Modifier = Modifier, onTileClick: (Int) -> Unit ) { // 各タイル(値)の現在位置を追跡 val tilePositions = IntArray(16) { -1 } repeat(PuzzleState.GRID_COUNT) { index -> tilePositions[puzzleState.tileAt(index)] = index } BoxWithConstraints( modifier = modifier .fillMaxWidth() .aspectRatio(1F) ) { val gridSize = maxWidth val tileSize = (gridSize - 12.dp) / 4 // 3 gaps of 4dp // 空白以外のタイルを描画(1-15の値ごとに) for (value in 1.until(PuzzleState.GRID_COUNT)) { val position = tilePositions[value] if (position == -1) continue val targetOffset = DpOffset( x = (tileSize + 4.dp) * (position % 4), y = (tileSize + 4.dp) * (position / 4) ) val animatedOffset by animateValueAsState( targetValue = targetOffset, typeConverter = TwoWayConverter( convertToVector = { AnimationVector2D(it.x.value, it.y.value) }, convertFromVector = { DpOffset(Dp(it.v1), Dp(it.v2)) } ), animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMedium ), label = "tile_$value" ) PuzzleTile( value = value, onClick = { onTileClick(position) }, modifier = Modifier .offset(x = animatedOffset.x, y = animatedOffset.y) .size(tileSize) ) } } } ともあれ、十数行のコードの追加と数行の変更で、コードの構成をほぼそのままにしたままアニメーションを表現できました。このアニメーションをカスタマイズしたい場合には、 animateValueAsState(...) の引数 typeConverter , animationSpec などを変更してみてください。このように、要領よくアニメーションを追加していくテクニックを以下の項でも紹介していきます。 ![パズルのアニメーション中](/assets/blog/authors/tsuyoshi_yamada/tech-blog_compose-animation_puzzle.webp =256x) Fig. 1: パズルのアニメーション中 3. 表示・非表示の切り替え時のアニメーションを追加する 先ほどのパズルゲームで、パズルを解いたときの表示があっさりしているので、少し凝ってみたいところです。凝る、といってもミニゲームですので、ささやかなものでよいでしょう。ひとまず、パズルが解けたときに飛び出してくるような演出を考えてみましょう。 これには簡単な方法が用意されています。 3.1. AnimatedVisibility(...) で囲み、内側で Modifier.animateEnterExit(...) を追加する if (solved) { ... } を AnimatedVisibility (solved) { ... } に替えるだけで、非表示から表示へ、および、表示から非表示へ変わるときにデフォルトのアニメーションが起こるようになります。デフォルトのアニメーションを変更するには、 AnimatedVisibility(solved) { ... } に囲まれた各composableの Modifier に対して animateEnterExit(...) を追加します: @Composable fun Puzzle15(modifier: Modifier = Modifier) { // ... Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { // ... AnimatedVisibility(solved) { Text( text = "🎉 Solved! 🎉", modifier = Modifier.animateEnterExit( enter = scaleIn( animationSpec = spring( dampingRatio = Spring.DampingRatioHighBouncy, stiffness = Spring.StiffnessMedium ) ), exit = None ), style = solvedStyle ) } } } Modifier.animateEnterExit(...) の enter と exit にはそれぞれ非表示から表示へ、表示から非表示へ変わるときのアニメーションを設定します。デフォルト値にはそれぞれ fadeIn() , fadeOut() が設定されています。この場合は表示時のみアニメーションを設定したいので、 exit のときはアニメーションせずに消えるように None を設定しています。 composableを上位のcomposableで囲ってスコープ ( AnimatedVisibilityScope ) を作り、囲われた個別のcomposableの Modifier に個別の設定を追加する、というコーディングは、Jetpack Compose の頻出テクニックですね。このあとにも同様のテクニックが登場します。 4. 画面更新時に古い画面が消えて新しい画面が現れるアニメーションを表現する 以下のコードは、上下左右の矢印ボタンをタップして2次元の整数座標平面の上を移動する様子を表現したものです: @Composable fun FlatField(modifier: Modifier = Modifier) { var xy by remember { mutableStateOf(Coordinates2D(0, 0)) } Box( modifier = Modifier .fillMaxSize() .background(xy.background) .safeContentPadding() ) { IconButton( modifier = modifier.align(Alignment.CenterStart), onClick = { xy = xy.goLeft() } ) { Icon( modifier = Modifier.size(64.dp), imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "goLeft", tint = xy.foreground ) } IconButton( modifier = Modifier.align(Alignment.TopCenter), onClick = { xy = xy.goUp() } ) { Icon( modifier = Modifier .size(64.dp) .rotate(90F), imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "goUp", tint = xy.foreground ) } IconButton( modifier = Modifier.align(Alignment.CenterEnd), onClick = { xy = xy.goRight() } ) { Icon( modifier = Modifier.size(64.dp), imageVector = Icons.AutoMirrored.Filled.ArrowForward, contentDescription = "goRight", tint = xy.foreground ) } IconButton( modifier = Modifier.align(Alignment.BottomCenter), onClick = { xy = xy.goDown() } ) { Icon( modifier = Modifier .size(64.dp) .rotate(90F), imageVector = Icons.AutoMirrored.Filled.ArrowForward, contentDescription = "goDown", tint = xy.foreground ) } Text( text = xy.coordinateString, modifier = Modifier.align(Alignment.Center), color = xy.foreground, fontSize = 64.sp ) } } 初期画面の白い場所から↓ボタンの赤い場所に移動し、続いて→ボタンで青い場所に移動し…という操作を繰り返せますが、動きがないと移動している感覚をつかみにくいように感じます。こういう時こそ、アニメーションの追加が効果を発揮します。 移動中の状態を表すクラスは以下のようになります: data class Coordinates2D(val x: Int, val y: Int) { val background: Color val foreground: Color val coordinateString: String init { val index = nonNegativeRemainder() background = backgroundColors[index] foreground = foregroundColors[index] coordinateString = coordinateString(x, y) } private fun nonNegativeRemainder(): Int = ((x + y) % 3).let { if (it < 0) it + 3 else it } fun goLeft() = Coordinates2D(x - 1, y) fun goUp() = Coordinates2D(x, y - 1) fun goRight() = Coordinates2D(x + 1, y) fun goDown() = Coordinates2D(x, y + 1) companion object Companion { private val backgroundColors = arrayOf(Color.White, Color(0xED, 0x29, 0x39), Color(0x00, 0x23, 0x95)) private val foregroundColors = arrayOf(Color.Black, Color.White, Color.White) private fun coordinateString(x: Int, y: Int) = if (x == 0 && y == 0) "O" else "(${x}, ${y})" } } 4.1. AnimatedContent(...) で囲み、古い画面から新しい画面へと切り替わるアニメーションを追加する 以下は FlatField(Modifier) の中の Box(...) を AnimatedContent(...) で囲ったものですが、アニメーションの定義に Box(...) のサイズの情報が必要なため Box(...) を BoxWithConstraints(...) に変え、 constraints を参照し maxWidth と maxHeight の値を使って AnimatedContent(...) の引数に与えています: @Composable fun FlatField(modifier: Modifier = Modifier) { var xy by remember { mutableStateOf(Coordinates2D(0, 0)) } var width by remember { mutableIntStateOf(0) } var height by remember { mutableIntStateOf(0) } AnimatedContent( modifier = modifier.fillMaxSize(), targetState = xy, transitionSpec = { val deltaX = targetState.x - initialState.x val deltaY = targetState.y - initialState.y slideIn { IntOffset(x = deltaX * width, y = deltaY * height) } togetherWith slideOut { IntOffset(x = -deltaX * width, y = -deltaY * height) } }, label = "coordinates2D" ) { targetXy -> BoxWithConstraints( modifier = Modifier .fillMaxSize() .background(targetXy.background) .safeContentPadding() ) { width = constraints.maxWidth height = constraints.maxHeight IconButton( modifier = Modifier.align(Alignment.CenterStart), onClick = { xy = targetXy.goLeft() } ) { Icon( modifier = Modifier.size(64.dp), imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "goLeft", tint = targetXy.foreground ) } IconButton( modifier = Modifier.align(Alignment.TopCenter), onClick = { xy = targetXy.goUp() } ) { Icon( modifier = Modifier .size(64.dp) .rotate(90F), imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "goUp", tint = targetXy.foreground ) } IconButton( modifier = Modifier.align(Alignment.CenterEnd), onClick = { xy = targetXy.goRight() } ) { Icon( modifier = Modifier.size(64.dp), imageVector = Icons.AutoMirrored.Filled.ArrowForward, contentDescription = "goRight", tint = targetXy.foreground ) } IconButton( modifier = Modifier.align(Alignment.BottomCenter), onClick = { xy = targetXy.goDown() } ) { Icon( modifier = Modifier .size(64.dp) .rotate(90F), imageVector = Icons.AutoMirrored.Filled.ArrowForward, contentDescription = "goDown", tint = targetXy.foreground ) } Text( text = targetXy.coordinateString, modifier = Modifier.align(Alignment.Center), color = targetXy.foreground, fontSize = 64.sp ) } } } AnimatedContent(...) の引数 transitionSpec で、古い画面を追い出す slideOut { ... } と 新しい画面を引き入れる slideIn { ... } を infix EnterTransition.togetherWith(ExitTransition) で合併してアニメーションを定義しています。 AnimatedContent(...) の引数 content に与えるラムダ式の引数 ( targetXy ) が追い出される画面の状態を保持しており、各 IconButton(...) の onClick で targetXy から得た新しい状態を xy に代入することで状態を更新します。 ![アニメーションによる疑似スクロール](/assets/blog/authors/tsuyoshi_yamada/tech-blog_compose-animation_pseudo-scroll_1.webp =256x) ![アニメーションによる疑似スクロール](/assets/blog/authors/tsuyoshi_yamada/tech-blog_compose-animation_pseudo-scroll_2.webp =256x) Fig. 2-1, 2-2: アニメーションによる疑似スクロール このコードで、仮に( xy = xy.goLeft() などと記述して) targetXy を使わないで(コンパイラの警告を無視して無理やり)ビルドすると、アニメーション中で追い出されていく古い画面が正しく表示されません。この書き方で、 AnimatedContent(...) がアニメーション中の状態を正しく管理してくれていることがわかります。このアニメーションをカスタマイズしたい場合には、 AnimatedContent(...) の引数 transitionSpec を変更するなどしてみてください。 アニメーションの追加で、位置を移動している感覚が一気につかみやすくなりました。この動きを確認していると、これは縦横2次元のページャーのようにも使えそうです。Jetpack Composeには、水平方向にスクロールする HorizontalPager 、垂直方向にスクロールする VerticalPager が用意されていますが、2次元のページャーは標準にはありません。それがアニメーションの追加だけでページャー風のUIを作れます。もちろんアニメーションを追加しただけですので、スワイプして隣のページの一部だけを見るような操作はできませんが、十数行の追加と若干の変更だけで、アプリの印象を大きく変えられます。 5. NavHost(...) での画面遷移の前後をつなぐアニメーション Compose Animation バージョン 1.10.0 から、 SharedTransitionLayout が安定版になりました。これはcomposableで作成した画面の 共有要素 を定義するものです。共有要素とは何か? それは、 Compose での共有要素の遷移 の中の短い動画で確認してください。 NavHost(...) を利用している場合のコード例を以下に示します: private val colorMap = mapOf( "赤" to Color.Red, "緑" to Color.Green, "青" to Color.Blue, "シアン" to Color.Cyan, "マゼンタ" to Color.Magenta, "黄" to Color.Yellow, "茶" to Color(132, 74, 43), "群青" to Color(76, 108, 179), "カーキー" to Color(197, 160, 90) ) @Composable fun GridTransform(modifier: Modifier = Modifier) { val navController = rememberNavController() NavHost( modifier = modifier .safeContentPadding() .fillMaxSize(), navController = navController, startDestination = ROUTE_SMALL_SQUARE ) { composable(route = ROUTE_SMALL_SQUARE) { val onClick: (String) -> Unit = { navController.navigate("$ROUTE_LARGE_SQUARE?$ARG_SHARED=$it") } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Column( modifier = Modifier .aspectRatio(1f) .padding(8.dp) .fillMaxSize() .background( color = MaterialTheme.colorScheme.primaryContainer, shape = RoundedCornerShape(16.dp) ) ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( "赤", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "緑", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "青", modifier = Modifier.weight(1f), onClick = onClick ) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( "シアン", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "マゼンタ", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "黄", modifier = Modifier.weight(1f), onClick = onClick ) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( "茶", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "群青", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( "カーキー", modifier = Modifier.weight(1f), onClick = onClick ) } } } } composable( route = "$ROUTE_LARGE_SQUARE?$ARG_SHARED={sharedKey}", arguments = listOf(navArgument("sharedKey") { type = NavType.StringType }) ) { entry -> val colorName = entry.arguments?.getString("sharedKey") ?: "" LargeSquare(colorName) { navController.popBackStack() } } } } @Composable fun ColorButton( colorName: String, modifier: Modifier = Modifier, onClick: (String) -> Unit ) { TextButton( modifier = modifier .padding(16.dp) .aspectRatio(1f) .background(color = colorMap[colorName]!!, shape = RoundedCornerShape(16.dp)), onClick = { onClick(colorName) } ) { Text(colorName, color = Color.White) } } @Composable private fun LargeSquare( colorName: String, modifier: Modifier = Modifier, onBack: () -> Unit ) { Box( modifier = modifier .padding(16.dp) .fillMaxSize() .aspectRatio(1f) .background(color = colorMap[colorName]!!, shape = RoundedCornerShape(16.dp)) .clickable { onBack() } ) { Text(text = colorName, modifier = Modifier.padding(8.dp), color = Color.White) } } 注意: このコードは、単純化のためバックボタンと9色のボタンの連打などの対策を省略しています。短時間での連打を避けてテストしてみてください。 初期画面で9色のボタンが表示され、ボタンをタップするとタップしたボタンの色の大きな正方形が現れます。大きな正方形が表示された状態でバックボタンを押す(またはバックジェスチャを行う)と9色のボタンの画面に戻ります。ここで、ボタンタップで大きな正方形の画面に戻るときと、バックボタンで9色のボタンの画面に戻るときにフェイドインとフェイドアウトのアニメーションが見られます。これは NavHost(...) の引数 enterTransition , exitTransition , popEnterTransition , popExitTransition , sizeTransform のデフォルト値で規定されています。この設定を、特定の画面遷移において以下のようなコードの追加と修正を加えることにより、特定の画面遷移前後の 共有要素 を関連づけるアニメーションで上書きすることができます。 NavHost(...) によるバックスタックの変化を伴わない画面遷移における 共有要素 アニメーションの実装は、 Compose での共有要素の遷移 を参照してください。下表の左から右、および右から左への画面遷移時に、左の画面でタップしたボタン(右下)によって現れた右の画面の正方形がまさに目的のコンテンツであったことがアニメーションによって視覚的に表現されます。 9色のボタンの画面 大きい正方形の画面 ![9色のボタンの画面](/assets/blog/authors/tsuyoshi_yamada/tech-blog_compose-animation_9-buttons.webp =256x) Fig. 3-1 ![大きい正方形の画面](/assets/blog/authors/tsuyoshi_yamada/tech-blog_compose-animation_large-square.webp =256x) Fig. 3-2 5.1. SharedTransitionLayout { ... } で囲み、画面要素を共有する ここでは、 NavHost(...) による画面遷移の前後での 共有要素 アニメーションの実装を紹介します: @Composable fun GridTransform(modifier: Modifier = Modifier) { val navController = rememberNavController() SharedTransitionLayout { NavHost( modifier = modifier .safeContentPadding() .fillMaxSize(), navController = navController, startDestination = ROUTE_SMALL_SQUARE ) { composable(route = ROUTE_SMALL_SQUARE) { val onClick: (String) -> Unit = { navController.navigate("$ROUTE_LARGE_SQUARE?$ARG_SHARED=$it") } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Column( modifier = Modifier .aspectRatio(1f) .padding(8.dp) .fillMaxSize() .background( color = MaterialTheme.colorScheme.primaryContainer, shape = RoundedCornerShape(16.dp) ) ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( this@composable, "赤", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "緑", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "青", modifier = Modifier.weight(1f), onClick = onClick ) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( this@composable, "シアン", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "マゼンタ", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "黄", modifier = Modifier.weight(1f), onClick = onClick ) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ColorButton( this@composable, "茶", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "群青", modifier = Modifier.weight(1f), onClick = onClick ) ColorButton( this@composable, "カーキー", modifier = Modifier.weight(1f), onClick = onClick ) } } } } composable( route = "$ROUTE_LARGE_SQUARE?$ARG_SHARED={sharedKey}", arguments = listOf(navArgument("sharedKey") { type = NavType.StringType }) ) { entry -> val colorName = entry.arguments?.getString("sharedKey") ?: "" LargeSquare(this, colorName) { navController.popBackStack() } } } } } @Composable fun SharedTransitionScope.ColorButton( animatedContentScope: AnimatedVisibilityScope, colorName: String, modifier: Modifier = Modifier, onClick: (String) -> Unit ) { TextButton( modifier = modifier .padding(16.dp) .aspectRatio(1f) .background(color = colorMap[colorName]!!, shape = RoundedCornerShape(16.dp)) .sharedBounds( sharedContentState = rememberSharedContentState(colorName), animatedVisibilityScope = animatedContentScope, boundsTransform = { _, _ -> tween(durationMillis = 500) }, enter = fadeIn(), exit = fadeOut(), resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds() ), onClick = { onClick(colorName) } ) { Text(colorName, color = Color.White) } } @Composable private fun SharedTransitionScope.LargeSquare( animatedContentScope: AnimatedVisibilityScope, colorName: String, modifier: Modifier = Modifier, onBack: () -> Unit ) { Box( modifier = modifier .padding(16.dp) .fillMaxSize() .aspectRatio(1f) .background(color = colorMap[colorName]!!, shape = RoundedCornerShape(16.dp)) .sharedBounds( sharedContentState = rememberSharedContentState(colorName), animatedVisibilityScope = animatedContentScope, boundsTransform = { _, _ -> tween(durationMillis = 500) }, enter = fadeIn(), exit = fadeOut(), resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds() ) .clickable { onBack() } ) { Text(text = colorName, modifier = Modifier.padding(8.dp), color = Color.White) } } SharedTransitionLayout { ... } で大外を囲み、 SharedTransitionScope の中で Modifier.sharedBounds(...) を使って 共有要素 の対応づけを行うのは NavHost(...) を使わない画面遷移の場合と同じです。 Modifier.sharedBounds(...) の引数 animatedVisibilityScope には NavGraphBuilder.composable(...) に由来する AnimatedContentScope ( this@composable ) をあてます。これで NavHost(...) による画面遷移時に共有要素間のアニメーションを表示できるようになります。 SharedTransitionScope.ColorButton(...) と SharedTransitionScope.LargeSquare(...) で、 rememberSharedContentState(Any) の引数 key にボタンの色の名前をあてることで、画面遷移前と遷移後の画面要素を共有していることを確かめてみてください。 共有要素アニメーションの設定のために新たに追加が必要なコーディングは、 SharedTransitionLayout { ... } で囲む 共有要素を定義するための sharedBounds(...) (or sharedElement(...) ) を設定し共有要素間で key を一致させる のために必要な SharedTransitionScope , AnimatedVisibilityScope の2つのスコープを composable に渡す です。これらはアニメーション設定前のコードの構成を大きく変えることなく実装可能でしょう。もし、既存のコードに対して少ない変更での適用が難しいようでしたら、変更が容易な構成になるようリファクタリングを試みてみてください。 共有要素アニメーションはうまくはまれば美しいですが、画面設計のイメージとぴったり合うアニメーションは難しいかもしれません。その場合は Modifier.sharedBounds(...) の引数 enter , exit , boundsTransform , resizeMode をいろいろと調整するなどしてみてください。 5.2. 予測型「戻る」との関係 共有要素アニメーションは、 NavGraphBuilder.composable(...) のデフォルトの画面遷移アニメーションを上書きします。加えて、予測型「戻る」アニメーションの有効化、すなわち、API level 33〜35 の AndroidManifest.xml において android:enableOnBackInvokedCallback="true" を指定している場合、または API level 36 以上の AndroidManifest.xml において android:enableOnBackInvokedCallback="false" を指定していない場合において、 NavHost(...) による戻るアニメーションも上書きします。予測型「戻る」アニメーションを有効にした状態でアプリをビルドし、端末をジェスチャーナビゲーションモードに設定して上記の大きな正方形の画面で「戻る」ジェスチャをゆっくりと実行すると、共有要素アニメーションがゆっくりと逆戻りしていくことが容易に確かめられます。また、API level 36 以上、かつ Android OS 16 以上でボタンナビゲーションモードに設定してバックボタンを長押しすると、共有要素の逆戻りアニメーションが見られるはずです。 このように NavHost(...) の「戻る」アニメーションは予測型「戻る」アニメーションの設定に影響を与えます。そのことに留意して、予測型「戻る」アニメーションの有効化設定を行うか否か決定する必要があります。API level 36 の時点では、 android:enableOnBackInvokedCallback="false" を指定することによって予測型「戻る」アニメーションを無効にできます。 6. まとめ 本記事では、小規模の変更で Jetpack Compose における実用的なアニメーションを実装できるテクニックを4例ほど紹介しました。アプリ内のアニメーションは、ほとんどの場合必須の機能ではないため、特にスケジュールに余裕のない開発プロジェクトでは実装が省略されがちですが、使いどころによっては小さくない使い勝手の向上をもたらし、アプリの印象を大きく向上させる力を秘めています。それらを少ない工数で可能にする手段が豊富にあれば、気軽に実装を試すことができます。Compose Animation のAPIの多くは、 宣言的 、すなわち、UI要素をアニメーションさせるよ、と宣言するような感覚でアニメーションを追加できるように工夫されており、細かい手続き的記述の中で動作を定義しなければならないような複雑さを回避しやすい設計になっています。それらを活用し、多くのアプリの品質向上に役立てられれば幸いです。 7. 参考文献 Android API reference Quick guide to Animations in Compose Animation modifiers and composables Add support for predictive back animations
アバター
この記事は KINTOテクノロジーズ Advent Calendar 2025 の7日目の記事です🎅🎄 はじめに こんにちは、KINTO テクノロジーズ Cloud Security グループの多田です。普段は 大阪 (Osaka Tech Lab) で勤務しています。 我々が開発する多くのサービスは、Amazon Web Services 上で開発していますが、昨今は生成 AI の活用も盛んで、OpenAI の利用に伴い、Microsoft Azure での開発も増えてきました。 本記事では、Azure Container Apps におけるワークロード保護の必要性から、Microsoft Defender for Cloud の限界、そして Sysdig Serverless Agent を用いた実践的な保護手法について、解説します。 Azure Container Apps とは? Azure Container Apps(以下、ACA)は、Microsoft が提供するサーバーレス型のコンテナ実行環境です。ACA の詳細については、Microsoft の ドキュメント を参照してください。 セキュリティ上の特徴としては、Kubernetes ベースのマネージドサービスであり、基盤レイヤー(ノード、ネットワーク、OS)のセキュリティは Microsoft の責任範囲となることです。一方で、アプリケーション層(コンテナワークロード)のセキュリティはユーザー側の責任となります。 この「共有責任モデル」においてアプリケーション層のワークロード保護をどう実現するかが、本記事の内容となります。 なぜワークロード保護が必要なのか? サーバレスな環境であっても、以下のようなセキュリティリスクは依然として存在します。 コンテナイメージの脆弱性 ランタイムの脅威 設定ミスや過剰な権限 特に、ランタイムの脅威については、以下のような脅威を検知し対処する必要があります。 暗号通貨マイニングの検知・防止 コンテナドリフト(不正な変更)の防止 不正なネットワーク通信の検知・防止 リバースシェル実行のブロック ファイルレス実行の検知 これらの脅威に対処するため、 ランタイムでの継続的な監視と脅威検知が不可欠となります。 ちなみに、ACA のセキュリティ機能については、 こちら を参照してください。 Microsoft Defender for Cloud ではワークロード保護ができるのか? 結論から言うと、 2025年11月時点では、Defender for Containers は ACA をサポートしていません。 Microsoft Defender for Containers は、Azure Kubernetes Service や Azure Container Registry、AWS EKS、Google GKE などに対応しています。詳細なサポート範囲については、 こちら を参照してください。 つまり、 アプリケーション層のワークロード保護を実現するには、サードパーティ製品の導入が推奨される ということになります。 Sysdig Serverless Agent によるワークロード保護 KINTO テクノロジーズでは、発見的ガードレール(CSPM)など、クラウドセキュリティの運用に Sysdig Secure を利用しています。 Sysdig Secure をどのように活用しているかは、過去にいくつかブログを投稿していますので、よろしければ検索してみてください。一番新しいものだと、「 LLM アプリケーションのセキュリティを保護する AI-SPM の取組み 」を投稿しています。 ACA のワークロード保護についても、Sysdig Secure が提供する Serverless Agent を利用して保護する取組みを進めています。 Sysdig Serverless Agent とは? Sysdig Serverless Agent は、サーバレス環境向けに設計されたランタイムセキュリティエージェントです。コンテナワークロードの監視、脅威検知等を実施することができます。 ACA への Serverless Agent のインストールや設定については、 SCSK 株式会社さんのブログ で丁寧に解説されていますので、そちらを参照してください。Serverless Agent についてイメージがつくと思います。 Serverless Agent は、ホストのカーネルにアクセスできないサーバレス環境において、 ユーザスペースレベル の監視を行います。 詳細なアーキテクチャについては、Sysdig の ドキュメント を参照してください。 この仕組みの重要なポイントは、コンテナの ENTRYPOINT で起動されたプロセスツリーのみが監視対象となる点です。そのため、 docker exec 経由で生成されたシェルや子プロセスはこの監視対象ツリーの外部で生成されるため、システムコールレベルの 挙動として検知されません。 Serverless Agent の検知の盲点 前述の通り、Serverless Agent は ENTRYPOINT で起動されたプロセスツリーを監視対象としています。攻撃者が何らかの方法(脆弱性悪用、認証情報搾取等)で、Azure コンソールや CLI へのアクセス権を取得した場合、以下のコマンドでコンテナ内部にアクセスできます。 az containerapp exec --name hogehoge-containerapp --resource-group hogehoge-resourcegroup --exec-command "/bin/bash" このように、コンテナ内部にアクセスできれば、Serverless Agent に検知されることなく、不正なアクティビティが可能となります。 では、exec 経由の攻撃をどう検知するか? exec 経由の攻撃は、Serverless Agent では検知できないため、Azure Activity Log(監査ログ)に記録される exec 操作そのものを検知することで、攻撃の予兆を検知します。 Sysdig Secure には、 Cloud Detection and Response と呼ばれる機能があり、監査ログをリアルタイムに監視することができます。 Sysdig の Cloud Detection and Response は、 Falco による脅威検知を実施しています。 az containerapp exec コマンドの実行や、Azure コンソール経由での exec を実施すると、Azure Activity Log には、以下のイベントが記録されます。 Microsoft.App/containerApps/getAuthToken/action このイベントを Falco ルールで検知することで、 exec 操作をリアルタイムに検知できます。Falco ルールは以下となり、Azure コンソール及び CLI 経由での exec を検知できるので、無許可の exec 操作があれば確認するなどの運用を実施することで対応が可能となります。 rules: - rule: Detect Azure ContainerApp AuthToken Succeeded desc: Detect when Azure Activity Log shows Microsoft.App/containerApps/getAuthToken/action with status Succeeded condition: > evt.type = "open" and json.value["operationName.value"] = "Microsoft.App/containerApps/getAuthToken/action" and json.value["status.value"] = "Succeeded" output: > Azure ContainerApp AuthToken request succeeded (operation=%json.value["operationName.value"], status=%json.value["status.value"], caller=%json.value["caller"]) priority: WARNING source: json tags: [azure, containerapp, auth, security] まとめ 本記事では、Azure Container Apps におけるワークロード保護の実践的なアプローチを解説しました。 Defender for Cloud は、Azure Container Apps に未対応 Sysdig Serverless Agent のようなサードパーティ製品でのワークロード保護が有効 Serverless Agent には、仕組み上、検知の盲点が存在する場合があるため、その理解が重要 盲点については、多層防御で脅威を検知し、カバレッジを高めることが大切 今回は、Azure Container Apps におけるワークロード保護について記載しました。 我々、Cloud Security グループは、マルチクラウド環境におけるセキュリティについて、日々実践しています。今後も当グループの取り組みをご紹介していきたいと思います。 最後までお読みいただきありがとうございました。
アバター
これは KINTOテクノロジーズ Advent Calendar 2025 の7日目の記事です🎄 1.はじめに KINTOテクノロジーズ、セキュリティ・プライバシー部のKa-Saiです。 私たちはトヨタグループの一員として金融を含むモビリティサービスを提供するにあたり、お客様やパートナー企業からお預かりした個人情報や機密情報、そしてそれらを支えるシステムといった「情報資産」を守ることを最重要テーマの一つとして位置づけ、次のミッションとビジョンを掲げて活動しています。 ミッション お客様に安心、安全なサービスを提供する。 私たちのミッションはブレーキを踏むことではなく、どうしたら安心・安全にアクセルを踏めるのかを考えることである。 ビジョン セキュリティ・バイ・デザイン、プライバシー・バイ・デザインを浸透させ、安心、安全なサービスが当たり前の世の中を作る。 ・・・・・ さて、セキュリティという言葉からは「スピードを落とすもの」「新しい挑戦を止めるもの」というイメージが先行しがちです。しかし私たちは セキュリティはビジネスの“ブレーキ”ではなく、“推進力”になりうる と考えており、このエントリーでは、 なぜセキュリティがブロッカーに見えがちなのか その構造的な課題に対し、当社がどのような哲学と設計で向き合っているか 具体的な組織・仕組み・取り組み そして、私たちが見据える未来 についてご紹介したいと思います。 2. 課題意識:従来型セキュリティのジレンマ 2-1. セキュリティはなぜ“遅くするもの”に見えるのか 多くの組織で、セキュリティは次のように捉えられがちです。 手続きが重く、レビューや承認でスピードが落ちる 新しいツール、クラウドサービス、生成AIなどにストップがかかる リリース直前に差し戻しが発生し、手戻りが大きくなる コストセンターとして扱われ、価値が見えにくい これらは、「後付けのセキュリティ」「人に依存した個別判定」「都度相談ベース」といった構造から生じる課題です。そしてもう一つ、私たちが強く意識しているのが、 「ルールは存在するだけで、暗黙のブロッカーになりうる」 という現実です。これは、ルールが存在するだけで、「なんとなく怖いからやめておこう」「ここに触れるのはやめておこう」という自己検閲が働きます。その結果、チャレンジもイノベーションも生まれにくくなります。 2-2. 飛躍成長に“スケールする”セキュリティへ 当社は、モビリティ×金融という高いレギュレーションと複雑なビジネス要件が絡み合う領域で、事業の飛躍的成長を目指しています。 新しい市場・国・サービス形態への展開にあたっては、セキュリティも同じスピードとスケール感で成長しなければなりません。 ここで重要になるのが、 「セキュリティもリスクだが、ビジネスの減速もまた大きなリスクである」 という視点です。 攻撃を受けるリスクだけでなく、 成長の推進力を失うリスク も、経営にとっては無視できません。 そこで私たちは、「いかに早く“セキュアなプロダクト”を世の中に出せるか」を重要視しています。 つまり、最初の段階から一定水準のセキュリティとコンプライアンスを満たした状態で、素早くマーケットに到達できるかという観点です。 新しいビジネス・機能を早く世に出す 同時に、金融・モビリティ領域に求められる厳しい安全基準を満たす この両方を満たすためには、「ビジネスゴールに直接貢献する目標」と「セキュリティの目標」を切り離さず、同じテーブルで設計するアプローチが必要です。 2-3. 従業員の負担をどう減らすか セキュリティ・プライバシー部は全知全能ではありません。すべてのコード、すべての設定、すべてのデータフローを当部だけで確認することは現実的ではなく、従業員一人ひとりの協力が不可欠です。 しかし従業員にはそれぞれ本来のミッションがあります。プロダクト開発・運用・ビジネス側のタスクも山積みです。 セキュリティ対応が「追加の仕事」として乗ってくる 手動のチェックや証跡作業が増える 要件理解に時間がかかり、本来の仕事が圧迫される 結果として、過剰または非対称な(かけた時間に対してインパクトが小さい)セキュリティ作業が発生しがちです。だからこそ、当社では CISOからのトップダウンな支援 現場への権限移譲と自律的なボトムアップの行動 この両方のバランスを取り、従業員の負荷を増やさずにセキュリティレベルを上げる設計を重視しています。 3. 当社のセキュリティ哲学:Security as Momentum 上述の課題を踏まえ、当部がたどり着いた結論はシンプルです。 セキュリティは、正しくデザインすれば、事業を加速させる推進力になる。 この哲学のもと、私たちは次のような考え方を採用しています。 3-1. “プラットフォーム”としてのセキュリティ 人が一つひとつチェックするのではなく、 仕組みやプラットフォームにセキュリティを埋め込む ことで、摩擦を最小化します。 安全なクラウド設計・設定をガイドラインとテンプレートに落とし込む CI/CD パイプラインに自動チェックを組み込む セキュアなデフォルト値を提供し、「何もしなければ安全」な状態をつくる 3-2. “止める人”ではなく“実現する人” セキュリティの役割は「No」を突きつけることではなく、「どうしたら安全にYesと言えるか」を一緒に考えることです。 企画段階からの相談を歓迎し、実現可能な選択肢を提示する “やってはいけないこと”だけではなく、“こうすればできる”を示す 3-3. 開発者体験としてのセキュリティ “セキュアな開発者の体験” を整えることは、結果的に開発組織全体の生産性向上につながるため、開発者にとっての「安心してアクセルを踏める環境づくり」になっているかを常に意識しています。 「このテンプレートを使えばOK」という明確なガードレール 面倒なセキュリティ評価の証跡取得作業を自動化 相談しやすいチャネルと、わかりやすいドキュメント 4. セキュリティ管理部署の組織体制:ビジネス成長と速度を支えるための構造 当社はトヨタ自動車の子会社であるトヨタファイナンシャルサービス(TFS)のグループ会社に属しています。TFSグループではサイバーセキュリティのリスクを最も重要な経営課題の一つとして位置づけ、グループ全体で対策を推進しており、この方針のもと、当社における情報セキュリティ管理は、TFSグループのセキュリティプログラムを基盤として運用されています。 4-1. GIS Standard:グローバル基準に基づくセキュリティフレームワーク そしてTFSグループでは、NISTやISO27001などの国際標準をベースに、金融サービスに最適化された独自のセキュリティ要件「GIS Standard(Global Information Security Standard)」を策定し、グループ全体で運用しています。 このGIS Standardには ガバナンス オペレーション テクニカルコントロール クラウドセキュリティ 生成AI などの新領域 といった多岐にわたるドメインをカバーする300以上の管理項目が含まれており、当社はこのGIS Standardを自社の標準として採用し、 グローバル水準の安全性と信頼性を前提条件として事業を進める ことを選択しています。 4-2. セキュリティ・プライバシー部の3つの専門グループ このGIS Standardというセキュリティフレームワークを現場で機能させるため、セキュリティ・プライバシー部は以下の専門グループで構成されています。 1.クラウドセキュリティG クラウドにおける設計・設定標準化(ガイドライン・ガードレール) CNAPP / CSPM / CIEM などの導入・運用 マルチクラウド環境のリスク監視 2.サイバーセキュリティG SOC業務(ログ収集・分析・インシデント対応) 脆弱性管理・診断(Red Team / Blue Team) シャドーIT対策 3.インフォメーションセキュリティG GIS StandardアセスメントのDX化 規程整備・リスク評価 情報セキュリティ教育の高度化 プライバシー影響評価(PIA) AI利用におけるデータ保護ガイドライン策定 知的財産権管理の仕組化 これらのグループが連携しながら、プロダクトチーム・コーポレート部門・グローバルの関連会社と伴走する体制を形作っています。 5. 当社の具体的な取り組み:セキュリティを推進力へ変える実装構造 5-1. プラットフォームセキュリティ:仕組みに埋め込むセキュリティ (1) クラウドセキュリティの標準化 AWS / Azure / GCPごとのセキュリティガイドライン ガードレールの「カイゼンガイド」 AIセキュリティガイドライン を整備し、「 これに沿って設計・実装すればGIS Standardやベストプラクティスを自然に満たせる 」状態を目指しています。 (2) CNAPP(Cloud-Native Application Protection Platform)の導入 クラウド設定の継続的評価 IAM権限の過剰状態の検知と是正 クラウド上のワークロード保護 ログ収集・分析による脅威検知 を自動化。「人が都度チェックする」から、「システムが常時監視し、必要に応じてアラートを上げる」体制へと進化しつつあります。 5-2. サイバーディフェンス:攻めと守りの両輪 (1) Red Team(攻め) 新規プロジェクトや診断未実施プロダクトへの積極的な脆弱性診断 SBOM・Secret管理・EOLなど、ソフトウェアサプライチェーンの観点も含めた検証 (2) Blue Team(守り) SIEM・EDR・Proxy・DNSなどを用いたログ監視・分析 インシデント対応計画の整備と演習 検知ルールの継続的なチューニング これらの取り組みにより、「攻撃を受けてから慌てて対応する」のではなく、 事前の備えと継続的なモニタリングで、組織全体の“余白”を生み出す ことを目指しています。 5-3. セキュリティ・プライバシーアセスメントDX:コンプライアンスを“副産物”にする (1) セキュリティガバナンス GIS Standardへの適合評価(アセスメント)の自動化 エビデンス収集のダッシュボード化 リスクベースアプローチによるアセスメント対象の整理 セキュリティ問い合わせの自動応答化 いわゆる”セキュリティガバナンス”を「DX(デジタルトランスフォーメーション)」の対象として捉え、 「監査対応のために証跡をかき集める」ような後ろ向きの作業 を減らし、「普段の運用がそのまま証跡になる」状態へと近づけています。 (2) プライバシーガバナンス / 知的財産の管理 プライバシー影響評価(PIA)の導入・運用 AI利用におけるデータ保護ガイドラインの策定・浸透 ライフサイクルに連動した網羅的な知財調査とログ管理 これにより、 「安心して使えるサービス」であることが、ビジネスの選ばれる理由になる状態 を目指しています。 6. おわりに:セキュリティを職人仕事から“標準化”、そして“推進力”へ セキュリティ・プライバシー部のミッションは、お客様に安心・安全なサービスを提供することです。 その達成のため、セキュリティは単なる「リスクの最小化」ではなく、 ビジネスの成長と推進力醸成にどう貢献できるか を常にSpeedとQualityの両面から考えています。 一方で、現実には   急速なビジネス成長への対応 従業員の負担軽減 法令要件への効率的な準拠 といったテーマのバランスをとることが、大きなチャレンジであることも事実です。私たちは、このチャレンジに対して 「職人仕事」から「標準化」へ 一部のセキュリティエキスパートだけに依存した属人的なセキュリティから、 誰もが同じ品質を出せる標準化された仕組みへ 従業員中心設計(Employee-Centered Design) セキュリティのために人を酷使するのではなく、 従業員が無理なく・誇りを持って取り組めるように設計する 自動化中心設計(Automation-First Design) トイル(自動化可能な業務)を減らし、クリエイティブな仕事に集中できるようにする アジャイルガバナンス 一度作ったルールも、“壊しながら”作り直し続ける姿勢を持つ という観点で取り組みを進めており、 私たちはQualityを高めるセキュリティを提供するだけでなく、Speedにも寄与し、事業を加速させる形でセキュリティを推進できる と信じてこれからもビジネス、開発、そしてお客様とともに歩んでまいります!
アバター
この記事は KINTOテクノロジーズ Advent Calendar 2025 の 6 日目の記事です🎅🎄 はじめに こんにちは。KINTO テクノロジーズ Cloud Security グループの渡邉です。 当社では AWS Organizations と Control Tower を利用したマルチアカウント運用を続けていますが、長年の積み重ねにより、一部の設計を見直す必要が出てきました。そこで、既存環境を前提にセキュリティガバナンス設計の再整理を進めることにしました。 本プロジェクトでは、いきなり全体を作り直すのではなく、影響の大きい領域から優先順位を付けて段階的に整理していく方針を取っています。そのうえで、安全性の判断を優先しつつ、新規 OU や AWS のマネージド機能の活用など、将来の運用改善につながる変更も一部取り入れています。 プロジェクトはまだ設計フェーズの段階であり、最終成果はこれからですが、本記事では現時点での意思決定の流れと背景を共有します。 ※ 本記事は AWS Organizations / AWS Control Tower / Security Hub CSPM / SCP / OU 設計に一定の理解がある読者を対象としています。 ※ プロジェクト前半である設計フェーズ時点の内容をベースにしており、今後の実装フェーズで変わる可能性があります。 設計項目の棚卸し まずは既存環境のセキュリティガバナンス要素を整理し、影響度と改善効果の観点から、以下の 5 項目を優先的に棚卸ししました。 最初の 3 項目は影響範囲が広く、クラウドセキュリティ以外のチームにも関係する領域です。後半の 2 項目は、それらに付随して検討が必要となる領域です。 OU 設計 └ OU 構造がそのままガードレール設計に影響するため 予防的ガードレール └ 望ましくない操作を事前にブロックする仕組みであり、既存アカウントに影響するため 発見的ガードレール └ 望ましくない設定を早期に検出する仕組みであり、予防的ガードレールと相互補完の関係にあるため 設定自動化ツール └ 新規アカウントの初期設定を自動化する社内ツール アカウント発行フロー └ 新規アカウント払い出しのプロセス 本記事では、それぞれの棚卸し過程で見えてきた課題と判断の背景を紹介します。 AWS セキュリティガバナンス構成の整理 この章では、当社の AWS セキュリティガバナンスがどのようなレイヤで構成されているかを整理します。 次の図は、 管理アカウントで適用される設定 ( AWS Organizations / AWS Control Tower / AWS CloudFormation ) それとは別に、設定自動化ツールによって管理アカウントおよびユーザアカウントに追加される設定 が、ユーザアカウント側で 3 段階の予防ガードレール ( 1 段目 : Control Tower 標準、2 段目 : 追加ガードレール、3 段目 : カスタムSCP ) 2 系統の検出 ( Security Hub CSPM 系統、Control Tower 系統) 監視・予防対象リソースの事前適正化 (設定自動化ツールによる初期設定) として作用する全体構造を示したものです。 図のとおり、当社環境では Control Tower の標準ガードレールに加えて、設定自動化ツールで補完した追加ガードレールやカスタム SCP により、3 段階の予防コントロールを構成しています。 また、Security Hub CSPM 系統と、Control Tower 系統という 2 系統の検出レイヤ、そして設定自動化ツールによるリソースの事前適正化も組み合わせることで、ガバナンス全体を構築しています。 これらは導入時期や目的が異なるため、OU や アカウント間で設定のばらつきが生じている部分もあります。 この図における主要な役割分担は、次の 5 つに整理できます。 Control Tower 標準ガードレール └ AWS が提供する予防・検出のベースライン 追加ガードレール (設定自動化ツールで有効化) └ 標準だけではカバーできない制約を追加するための予防・検出ガードレール カスタムSCP (設定自動化ツールから設定) └ 例外的な要件に対応するための当社独自の制限 Security Hub CSPM 系統 ( CSPM 側の検出レイヤ) └ AWS Config と連携し、設定ミスやベストプラクティス違反を継続的に検出するサービス Control Tower 系統 ( Control Tower 側の検出レイヤ) └ AWS Config と連携し、Control Tower のガードレールに紐づく検出コントロールを実現 こうした多層構造は AWS ガバナンスでは珍しくない構成ですが、棚卸しを進めるうえでは、どのレイヤがどのコントロールを提供し、各設定が Control Tower / Security Hub CSPM / 設定自動化ツールのどこに属しているのかを明確に整理する必要があり、一定の複雑さを伴いました。 今回は、まず Control Tower の追加ガードレール、特に影響の大きい予防コントロールを最新の状態へ追従させることを優先しました。レイヤ間の重複や役割分担の最適化については、今後の改善テーマとして段階的に進めていく方針としています。 OU 設計の棚卸し OU 設計は Control Tower の動作と密接に関係するため、設計変更時の影響範囲を見極めるのが特に難しい領域です。 特に影響が大きく、判断を難しくしたのは以下の 3 点です。 一部の動作がドキュメント化されておらず、事前に挙動を読み切れない場面がある点 └ 例えば、OU 名の変更後に必要となるランディングゾーンのリセット時に、何が行われるのかに関する詳細仕様が公開されていない点など OU ごとの差異に加え、過去の実装や運用の積み重ねが影響しており、OU 内のアカウントを別 OU に移動する際に、どの設定がどう再適用されるかを個別確認する必要がある点 本番アカウントや Control Tower の履歴を含む状態を検証環境で正確に再現できないため、検証だけでは安全性を保証しきれない点 └ 例えば、本番アカウントにおいて、Control Tower の運用に必要なリソースが、過去の運用の中で何らかの理由により変更・削除されているケースなど 上記を踏まえ、今後作成されるアカウントの収容先として、次の 3 パターンを比較しました。 ここでの 3 パターンは次のようなイメージです。 既存 OU (現状維持) : いま使っている OU 構造やガードレールを変えずに、新規アカウントを収容していく。 既存 OU (新ガードレール適用) : いま使っている OU 群に新しいガードレールを適用し、新規アカウントを収容していくことで、既存アカウントも含めて一気に状態を揃える。 新規 OU 群 : 既存 OU 群は現状維持として、新しいガードレールを適用した OU 群を新たに作成し、その OU に新規アカウントを収容していく。 No 収容先 変更の影響 長期の健全性 導入容易性 コメント 1 既存 OU (現状維持) ◎ 既存への影響なし × 負債が残る ◎ 現状維持で容易 一時的には安全だが長期負債が増える 2 既存 OU (新ガードレール適用) × 既存全体へ影響大 ◎ 健全性が高い × 検証負荷が大きい 理想的だが影響が重く段階移行が必要 3 新規 OU 群 ◎ 既存への影響なし △ OU 分散リスク ◎ 導入・検証が容易 OU 分散を後続フェーズで解消する必要あり 今回は、既存環境への影響を避けつつも新たな仕組みの導入が容易な案 3 を採用しました。 ただし、これは恒久的な解ではなく、既存 OU をいきなり組み替えずに新しいガードレール構成と運用モデルを検証するための最初の一歩という位置付けです。 新規 OU 群で運用実績と検証結果を蓄積したうえで、Sandbox OU など影響の小さい既存アカウントから段階的に移設し、最終的には OU 構成とガードレールを可能な限り統一していくことを中長期の方針としています。 予防的ガードレールの棚卸し 予防的ガードレールは、望ましくない操作や構成を未然に防ぐための仕組みです。当社環境では、AWS Control Tower の標準ガードレールに加え、その不足分を補完するため、設定自動化ツールで追加のガードレール有効化・カスタム SCP を適用しています。 段 名称 主体 役割 1 段目 標準ガードレール AWS Control Tower AWS 標準の予防コントロール 2 段目 追加ガードレール 設定自動化ツール ( CDK ) 標準ガードレールでカバーできない不足分の補完 3 段目 カスタムガードレール 設定自動化ツール ( CDK ) 当社固有の要件に合わせた独自の SCP 新規アカウントを新設 OU に収容する方針としたため、新規 OU に設定するガードレール追加に伴う既存環境への影響はありません。そのため、初期導入後に追加された Control Tower の新しい予防コントロールを有効化すべきかどうかを検討しました。 しかしながら、AWS が提供する「重大度」だけでは、以下に挙げる 3 つの例のように、なぜその評価になっているのか、実運用でどのような影響が出るのかまでは見えませんでした。 重大度が高だが、導入判断がつかないものの例 (1) [CT.EC2.PV.4] Require that Amazon EBS direct APIs are not called (2) [CT.S3.PV.2] Require all requests to Amazon S3 resources use authentication based on an Authorization header 重大度が中だが、導入しても良いと思われるものの例 (3) [CT.EC2.PV.11] Disallow public sharing of Amazon Machine Images ( AMIs ) そこで、Amazon Q Developer Pro ※ を用いて、各コントロールの SCP を、運用影響・推奨度・導入プロセスの観点で評価しました。上記 1~3 については、次のような気付きがありました。 EBS direct APIs を利用する AWS Backup パートナー製品などのバックアップに影響する可能性があるため注意が必要 ( CT.EC2.PV.4 ) Presigned URL が使えなくなる ( CT.S3.PV.2 ) 導入は妥当であるが、SCP ではなく DECLARATIVE_POLICY なので挙動が異なる点に注意が必要 ( CT.EC2.PV.11 ) こうした整理を通じて、有効化が推奨され、影響が小さいものは、新規 OU で積極的に有効化する方針としました。 ※ Amazon Q Developer Pro を利用した理由は、他の AI と比較すると応答速度は遅いものの、AWS の公式ドキュメントを都度参照したうえで回答していると推察でき、仕様変更の多い領域でも一定の安心感があったためです。 発見的ガードレールの棚卸し 当社環境では、セキュリティ基準の一元管理と自動更新性の観点から Security Hub CSPM をセキュリティ監査の主軸としています。 Control Tower の検出コントロールは、Control Tower が提供するガードレールや共通基盤の構成に関する観点を含むチェック群であり、当社では Security Hub CSPM を補完する仕組みとして位置付けています。 今回の見直しでは、運用開始後に追加された Control Tower の検出コントロールについて、「重大度:重大」または「ガイダンス:強く推奨」に該当するものを対象に、有効化の要否を確認しました。 その結果、例えば以下のようなコントロールは、Security Hub CSPM の基準にて既に監査可能であることを確認できたため、Control Tower 側では重複して有効化しない方針としました。 [CONFIG.KMS.DT.1] Checks if AWS Key Management Service ( AWS KMS ) keys are not scheduled for deletion in AWS KMS [CONFIG.KMS.DT.2] Checks if the AWS KMS key policy allows public access 一方、以下のように、サービス固有の設定を扱う一部のコントロールについては、当社での実際の利用状況や将来の利用計画に応じて、必要性を個別に検討する方針としました。 [CONFIG.EMR.DT.1] Checks if an account with Amazon EMR has block public access settings enabled 設定自動化ツールの棚卸し 当社では、新規アカウントの初期設定を自動化するため、設定自動化ツール ( CDK ベース) を開発・運用しています。長年にわたり、このツールが新規アカウントの標準化や初期設定の抜け漏れ防止に大きく寄与してきました。 今回の棚卸しでは、現在のアカウント規模やチーム構成、AWS のマネージド機能の拡充状況を踏まえ、この仕組みをどこまでシンプルにできるかを整理しています。 一般的には自動化範囲を広げる事例も多いですが、当社では維持しやすさを優先し、必要な部分に絞る方向性としました。 設定自動化ツールで実施している内容は以下の通りです。 追加の予防的ガードレール適用 カスタム SCP の追加 Security Hub CSPM の重複・不要なアラーム抑制 Security Hub CSPM 準拠を目的としたサービス設定 (例:デフォルト暗号化やパブリックアクセスブロックの有効化など) その他のセキュリティ設定の自動適用 現時点の方針としては、以下の 3 段構えで進める想定です。 AWS のマネージド機能に任せられる部分は、できる限り移行する マネージド機能では置き換えられない部分は、宣言的 IaC ( CloudFormation、CloudFormation StackSets、Terraform など) での管理を優先する IaC にも寄せられない例外的な処理は、最小限のコードとして残し、具体的な実装方式は後続フェーズで判断する 単にコードを減らすことが目的ではなく、組織として長期的に持ち続けるべき責務だけをコードに残すことを狙いとしています。 特に項目 4 については、 Security Hub CSPM の中央設定 に移行することで、一部の設定を AWS のマネージド機能に寄せられる可能性があります。既存 OU ではコントロールの状態に個別差分が残っており、適用には棚卸しが必要ですが、新規 OU から段階的に活用する予定です。 また、5 の中で実施している S3 ブロックパブリックアクセス有効化の設定についても、先日公開された S3 ポリシー への移行により、同様に AWS のマネージド機能に寄せられる可能性があります。こちらも今後、導入可否についての調査を進めていく予定です。 なお、設定自動化ツールには CDK による条件分岐など独自の強みがあり、これが見直しにおける判断ポイントとなる可能性があります。引き続き、上記方針が適切かどうかを慎重に検証しながら進めていく予定です。 アカウント発行フローの棚卸し 当社では、これまで新規アカウント発行時にルートユーザの MFA を有効化する運用を行っていましたが、物理作業が伴うため、発行の都度一定の稼働が発生していました。 2024年末に メンバーアカウントのルートアクセス一元管理 により、この運用自体を見直せる可能性があります。 今回の再設計と併せて、アカウント発行フローもできる限りシンプルかつ安全な形に整理することを検討しています。 まとめ 今回の棚卸しでは、長年の運用で積み上がった設定や仕組みを前提に、将来の拡張性と保守性の両面から、どこを変え、どこを維持すべきかを整理しました。 既存 Organizations を維持したまま理想的な構成へ作り替えることは簡単ではありません。OU 設計、ガードレール、初期設定の自動化ツールといった複数レイヤが相互に依存しているため、どれか一つを更新すると他のレイヤにも影響が波及するからです。 そのため今回は、すべてを全面刷新するのではなく、本番を止めずに少しずつ負債を減らしながら改善を進めるという方針で整理を進めました。特に Control Tower の挙動や一部のガードレールの影響範囲は、公開情報だけでは読み切れない部分もあるため、リスクを分割しながら段階的に検証する進め方が不可欠だと感じています。 今後は、AWS が提供するマネージド機能を積極的に活用しつつ、既存環境との整合を取りながら変更箇所を局所化し、段階的に改善を続けていく方針です。 本記事が、既存環境を前提にガバナンスを再設計する方々にとって、少しでも判断材料や視点のヒントになれば幸いです。
アバター