Slide 1

Slide 1 text

株式会社Relic 保 龍児(エイミ/amixedcolor) 不要なリソースを 自動で定期的に整理する方法 ~Sandboxアカウントのコストを削減しよう!~

Slide 2

Slide 2 text

2 アカウントにおけるコスト削減を 不要なリソースの自動整理によって 自分で実現できるようになる 今日のゴール

Slide 3

Slide 3 text

3 アカウントにおけるコスト削減を 不要なリソースの自動整理によって 自分で実現できるようになる 今日のゴール

Slide 4

Slide 4 text

4 • アカウントの 不要リソースを削除 したい • お試しのリソースがたくさん作成されるようなアカウント ex) Sandboxアカウント・Dev環境アカウント • (複数アカウントにまたがる不要リソースを削除したい) • 不要なリソースの削除で コストを削減 したい • 不要なリソースを 自動で 削除する方法を知りたい • 不要なリソースの削除を できる ようになりたい • 具体的なソースコード・ロジック・API • 具体的な組織内での運用方法 想定する聴衆

Slide 5

Slide 5 text

5 保 龍児(エイミ/amixedcolor) 業務内容 : プラットフォームエンジニアリング、 エンジニアイネーブルメントなど 好きなトピック : アジャイル、スクラム、新規事業開発 よくいるコミュニティ : アジャイルコミュニティ、 AWSコミュニティ 自己紹介 @amixedcolor

Slide 6

Slide 6 text

6 • X(旧Twitter)のDM • Xにて@メンション • 私の発表時間帯付近の #jawsfesta2024 #jawsfesta2024_c を付けた投稿 • できる限り全部追います!! • この後の休憩時間・会場で見かけたとき • ぜひ話しかけてください!! • 懇親会などなど全部参加します! • もちろんこちらでもぜひ!! ご質問・ご意見お待ちしております!! @amixedcolor エイミ

Slide 7

Slide 7 text

7 自動で整理した結果どれくらい削減した? 約 60 % 削減!

Slide 8

Slide 8 text

8 • AWS初心者(私エイミ)の1ヶ月 • 今回のシステムの運用費用 • Lambda実行料金など • 今回のシステムの開発時に検証用で立てたリソースの費用 • Aurora Serverless v2 0.5ACU-1.0ACU 数分間など 削減するために費やしたコストは?

Slide 9

Slide 9 text

9 • AWS SAM (Serverless Application Model) • EventBridge • Lambda • Javascript • Node.js • Jest • IAM • Slack App • Incoming Webhook 使用技術は?

Slide 10

Slide 10 text

10 • AWSコンソールほぼ触ったことない • SAMと言われてピンとこない • Lambdaと言われてピンとこない • AWS SDK使ったコード書いたことない • 新卒2年目 • Laravelアプリケーション開発PJに配属直後から従事 • AWSとプラットフォームエンジニアリングに興味を持ち異動 • Python・C・JS・PHPを中心に触ったことがある 実装開始時点のスキルは?

Slide 11

Slide 11 text

11 アカウントにおけるコスト削減を 不要なリソースの自動整理によって 自分で実現できるようになる 今日のゴール

Slide 12

Slide 12 text

12 アカウントにおけるコスト削減を 不要なリソースの自動整理によって 自分で実現できるようになる 今日のゴール(イマココ)

Slide 13

Slide 13 text

13 • 開発者が自由に使える環境、サンドボックス環境 • リソースがどんどん作られる • 放置するとコストがどんどん増えていく • 開発者で管理して削除して欲しい • ルールを作る • 「1日で削除 Must!」 • 「タグ付けの徹底 Must!」 • 「 2日以上に渡って何らかのリソースを利用する場合、予算を出して 上長に確認するのをお忘れなく」 開発の経緯

Slide 14

Slide 14 text

14 どうなる?

Slide 15

Slide 15 text

15 • 何日も削除されない 削除Must! • タグ付けがされたリソースはほとんどない タグ付けMust! • 管理コストは日に日に増えていく • 全てのエンジニアがサンドボックスを利用可能 • 会社が大きくなってエンジニアも増加 • 適切に削除してくれる人は少ない ルールを作った結果……機能しない!

Slide 16

Slide 16 text

16 気づけばコストは半年前の10倍に!!

Slide 17

Slide 17 text

17 どうしよう?

Slide 18

Slide 18 text

18 消せるようにする

Slide 19

Slide 19 text

19 ポイント • 都度消して良いか許可を取るのではなく、消すなと言われていなけれ ば消す →自動で消せるように • 消す場合でも事前に消す対象を通知する →納得感を持ってもらう 具体的には? • Deletion Policyタグを設定していなければ消す • Deletion Policyタグで「YYYYMM」を設定した月までは残す • YYYY = 年、MM = 月(ゼロ埋め) • フォーマットが沿っていない場合は見直すようARNを通知する • 消す2週間前時点で、消す予定のリソースのARNを通知する リソースを消せるようにする

Slide 20

Slide 20 text

20 アカウントにおけるコスト削減を 不要なリソースの自動整理によって 自分で実現できるようになる 今日のゴール(ココマデ)

Slide 21

Slide 21 text

21 アカウントにおけるコスト削減を 不要なリソースの自動整理によって 自分で実現できるようになる 今日のゴール(イマココ)

Slide 22

Slide 22 text

22 自動整理の全体像

Slide 23

Slide 23 text

23 起点となるEventBridge

Slide 24

Slide 24 text

24 各種Lambda関数

Slide 25

Slide 25 text

25 IAMロールとポリシー

Slide 26

Slide 26 text

26 通知フロー(1/6)

Slide 27

Slide 27 text

27 通知フロー(2/6)

Slide 28

Slide 28 text

28 通知フロー(3/6)

Slide 29

Slide 29 text

29 通知フロー(4/6)

Slide 30

Slide 30 text

30 通知フロー(5/6)

Slide 31

Slide 31 text

31 通知フロー(6/6)

Slide 32

Slide 32 text

32 削除フロー(1/5)

Slide 33

Slide 33 text

33 削除フロー(2/5)

Slide 34

Slide 34 text

34 削除フロー(3/5)

Slide 35

Slide 35 text

35 削除フロー(4/5)

Slide 36

Slide 36 text

36 削除フロー(5/5)

Slide 37

Slide 37 text

37 アカウントにおけるコスト削減を 不要なリソースの自動整理によって 自分で実現できるようになる 今日のゴール(ココマデ)

Slide 38

Slide 38 text

38 アカウントにおけるコスト削減を 不要なリソースの自動整理によって 自分で実現できるようになる 今日のゴール(イマココ)

Slide 39

Slide 39 text

39 • Resource Explorer APIの概要と利用方法 • タグ付けを用いたリソースの判別ロジック • 削除対象のものを判別 • 書式が沿っていないものを判別 • 削除関数の実装方法と使い方 • リソースタイプごとに実装すること • 自分を削除しないこと • テストの方法 • 自動テスト • 手動テスト 自分で実現できるようになるために(1/2)

Slide 40

Slide 40 text

40 • 組織内で定める運用ルール • タグ付けの依頼 • 「何もしていない」は「削除して良い」とする • 削除する前に通知をする • 複数アカウントにまたがる場合の概要紹介 自分で実現できるようになるために(2/2)

Slide 41

Slide 41 text

41 • Resource Explorer APIの概要と利用方法 • タグ付けを用いたリソースの判別ロジック • 削除対象のものを判別 • 書式が沿っていないものを判別 • 削除関数の実装方法と使い方 • リソースタイプごとに実装すること • 自分を削除しないこと • テストの方法 • 自動テスト • 手動テスト 自分で実現できるようになるために(1/2)

Slide 42

Slide 42 text

42 • そもそもResource Explorerとは? • リソース検索および検出サービス • 下記の項目などでフィルタできる • アカウントID • リージョン • リソースタイプ • サービス • リージョンを跨いだ検索 ができる • 「aws リソース一覧 api」で調べると? • Resource Groups Tagging APIが出てくるが、一度以上タグがついた リソースしか取得できない Resource Explorer APIの概要(1/2)

Slide 43

Slide 43 text

43 • Resourceの一覧を取得するならResource Explorer • APIはあるのか?→ある!メリットが大きい • 複数のサービスを 1つのAPIで 扱える • 簡単に 全リージョンのリソースを 一覧で取得できる • 自動整理する上で特定のリソースタイプに絞る • 料金がかかりがちなリソースタイプは絞られる • リソースタイプについて • Resource Explorer で検索できるリソースタイプ(基本的にほぼ全て検索可能) • https://docs.aws.amazon.com/ja_jp/resource- explorer/latest/userguide/supported-resource-types.html • 他のリソースタイプとして表示されるリソースタイプもある Resource Explorer APIについて(2/2)

Slide 44

Slide 44 text

44 レスポンスは?

Slide 45

Slide 45 text

45 Resource Explorer API: Searchの返すレスポンス(1/3) { "Count": { "Complete": boolean, "TotalResources": number }, "NextToken": "string", …

Slide 46

Slide 46 text

46 Resource Explorer API: Searchの返すレスポンス(2/3) … "Resources": [ { "Arn": "string", "LastReportedAt": "string", "OwningAccountId": "string", "Properties": [ 次のページで説明 ], "Region": "string", "ResourceType": "string", "Service": "string" } ], "ViewArn": "string" } Resourcesが主に使用する部分

Slide 47

Slide 47 text

47 Resource Explorer API: Searchの返すレスポンス(3/3) … "Properties": [ { "Data": [ { "Key": ”string", "Value": "string" } ], "LastReportedAt": "string", "Name": "string" } ] … この部分はリソースタイプによって違うが、 今回利用したリソースタイプでは全て、タグ のKeyとValueをもつ辞書のリストだった

Slide 48

Slide 48 text

48 利用方法は?

Slide 49

Slide 49 text

49 ソースコードを読み解く

Slide 50

Slide 50 text

50 ソースコードを読み解いてできるようになる 使用技術 • JavaScript • Node.js • Jest 注意 • 完全に一致したコードではなく抜粋しています • 読みやすさのための改変があります • 説明はAWSから離れてしまうためほとんどしません • もちろんスライドは公開するので、ご覧いただくか、お気軽にご質問ください! export const HelloJawsFesta2024InHiroshimaHandler = async (event) => { return { 'statusCode': 200, 'body': 'Hello JAWS FESTA 2024 in Hiroshima!' }; }

Slide 51

Slide 51 text

51 リソースタイプを絞って変数定義する const targetResources = [ 'apigateway:restapis', 'ec2:elastic-ip', 'ec2:natgateway', 'ec2:instance', 'ec2:volume', // EBS volume 'ec2:vpc-endpoint', 'elasticache:cluster', 'elasticache:replicationgroup', 'elasticloadbalancing:loadbalancer', 'elasticloadbalancing:loadbalancer/app', 'elasticloadbalancing:loadbalancer/net', 'lambda:function', 'rds:cluster', // RDS DB Cluster 'rds:db', // RDS DB Instance ];

Slide 52

Slide 52 text

52 Resource Explorerを使う import { ResourceExplorer2Client, SearchCommand } from "@aws-sdk/client-resource-explorer-2"; const params = { QueryString: "resourcetype:" + targetResource, } const command = new SearchCommand(params); let resources = []; do { const response = await client.send(command); resources = resources.concat(response.Resources); params.NextToken = response.NextToken; await new Promise(resolve => setTimeout(resolve, 400)); } while (params.NextToken);

Slide 53

Slide 53 text

53 • Resource Explorer APIの概要と利用方法 • タグ付けを用いたリソースの判別ロジック • 削除対象のものを判別 • 書式が沿っていないものを判別 • 削除関数の実装方法と使い方 • リソースタイプごとに実装すること • 自分を削除しないこと • テストの方法 • 自動テスト • 手動テスト 自分で実現できるようになるために(1/2)

Slide 54

Slide 54 text

54 削除対象のものを判別するロジック 削除対象 である タグを 持っていない 年月が過去 対象のリソース1つにおける、 全てのKeyが「Deletion Policy」と異なること タグを 持っている 書式が正しい 指定した月の 翌月頭が 今以前 対象のリソース1つにおける、 いずれかのKeyが「Deletion Policy」と一致すること YYYYMM 6文字 number型 である 01 - 12 の間 OR YYYYMM その月いっぱいは保持 翌月頭 now() <= AND

Slide 55

Slide 55 text

55 タグを持っていない

Slide 56

Slide 56 text

56 タグを持っていない const resourcesWithoutDeletionPolicy = resources.filter((resource) => { return resource.Properties.every((jsonElement) => { return jsonElement.Data.every((element) => { return element.Key !== "Deletion Policy"; }); }); });

Slide 57

Slide 57 text

57 年月が過去である

Slide 58

Slide 58 text

58 年月が過去: タグを持っている const resourcesWithDeletionPolicy = resources.filter((resource) => { return resource.Properties.some((jsonElement) => { return jsonElement.Data.some((element) => { return element.Key === "Deletion Policy"; }); }); });

Slide 59

Slide 59 text

59 年月が過去: 書式が正しい export const isValidDate = (value) => { const is6digit = value.length === 6; if (!is6digit) { return false; } const isNumber = typeof parseInt(value) === "number"; if (!isNumber) { return false; } const MM = value.slice(4, 6); const Month = parseInt(MM); const isValidMonth = 1 <= Month && Month <= 12; if (!isValidMonth) { return false; } return true; }

Slide 60

Slide 60 text

60 年月が過去: 指定した月いっぱい <=now() const YYYY = dateFormattedByYYYYMM.slice(0, 4); const MM = dateFormattedByYYYYMM.slice(4, 6); // 日本時間のその月の初日の0時0分とする const dateFormattedByISO8601 = YYYY + "-" + MM + "-01T00:00:00.001+09:00"; const parsedDateAsBeginOfMonth = new Date(Date.parse(dateFormattedByISO8601)); const parsedDateAsBeginOfNextMonth = new Date(parsedDateAsBeginOfMonth.setMonth( parsedDateAsBeginOfMonth.getMonth() + 1 )); return parsedDateAsBeginOfNextMonth <= now;

Slide 61

Slide 61 text

61 • Resource Explorer APIの概要と利用方法 • タグ付けを用いたリソースの判別ロジック • 削除対象のものを判別 • 書式が沿っていないものを判別 • 削除関数の実装方法と使い方 • リソースタイプごとに実装すること • 自分を削除しないこと • テストの方法 • 自動テスト • 手動テスト 自分で実現できるようになるために(1/2)

Slide 62

Slide 62 text

62 書式が沿っていないものを判別するロジック 書式が 沿って いない タグを 持っている 書式が 正しくない 書式が正しい 対象のリソース1つにおける、 いずれかのKeyが「Deletion Policy」と一致すること YYYYMM 6文字 number型 である 01 - 12 の間 AND NOT

Slide 63

Slide 63 text

63 書式が沿っていない

Slide 64

Slide 64 text

64 書式が沿っていない: タグを持っている const resourcesWithDeletionPolicy = resources.filter((resource) => { return resource.Properties.some((jsonElement) => { return jsonElement.Data.some((element) => { return element.Key === "Deletion Policy"; }); }); });

Slide 65

Slide 65 text

65 書式が沿っていない: NOT (書式が正しい) 書式が正しい export const isValidDate = (value) => { const is6digit = value.length === 6; if (!is6digit) { return false; } const isNumber = typeof parseInt(value) === "number"; if (!isNumber) { return false; } const MM = value.slice(4, 6); const Month = parseInt(MM); const isValidMonth = 1 <= Month && Month <= 12; if (!isValidMonth) { return false; } return true; } NOT (書式が正しい) return !isValidDate(element.Value)

Slide 66

Slide 66 text

66 • Resource Explorer APIの概要と利用方法 • タグ付けを用いたリソースの判別ロジック • 削除対象のものを判別 • 書式が沿っていないものを判別 • 削除関数の実装方法と使い方 • リソースタイプごとに実装すること • 自分を削除しないこと • テストの方法 • 自動テスト • 手動テスト 自分で実現できるようになるために(1/2)

Slide 67

Slide 67 text

67 1. リソースタイプごとに実装する • AWSにはARNなどでリソースを一元的に削除できるAPIはない (現時点で) • 各リソースのAPIには削除メソッドが用意されているのでそれを使う 2. 削除ハンドラから使い分ける • サービスとリソースタイプで対応する関数を変数で切り替えて使う 3. エラーハンドリングする • どれかで失敗しても処理を続ける • エラーが出ても、削除対象のリソース全ての削除を試みる • エラー時に一定の時間をおいて繰り返す • 削除順やレート制限など、一時的なエラーであることがある リソースタイプごとに実装すること

Slide 68

Slide 68 text

68 リソースタイプごとに実装する

Slide 69

Slide 69 text

69 リソースタイプごとに実装する: EC2インスタンス削除の例 const extractInstanceId = (arn) => { const parts = arn.split('/'); const instanceId = parts[parts.length - 1]; return instanceId; } export const deleteEc2Instance = async (resource) => { const client = new EC2Client({ region: resource.Region }); const command = new TerminateInstancesCommand({ InstanceIds: [extractInstanceId(resource.Arn)] }); try { const response = await client.send(command); return response; } catch (error) { throw error; } };

Slide 70

Slide 70 text

70 削除ハンドラから使い分ける

Slide 71

Slide 71 text

71 削除ハンドラから使い分ける: 実装した関数を辞書の値にする const implementedFunctions = { "apigateway": { "restapis": deleteApigatewayRestapis, }, "ec2": { "elastic-ip": deleteEc2ElasticIp, "natgateway": deleteEc2NatGateway, "instance": deleteEc2Instance, "volume": deleteEc2EbsVolume, "vpc-endpoint": deleteVpcEndpoint, }, "elasticache": { "cluster": deleteElasticacheCluster, // Memcached cluster "replicationgroup": deleteElasticacheReplicationGroup, // Redis cluster }, … }

Slide 72

Slide 72 text

72 削除ハンドラから使い分ける: 対応する関数を変数に入れる Object.keys(uniqueResourceTypesByService).forEach( (service) => { uniqueResourceTypesByService[service].forEach( (resourceType) => { if (isDeletionImplemented(service, resourceType)) { const deletionFunction = implementedFunctions[targetService][targetResourceType]; groupedResources[service][resourceType].forEach( (resource) => { …(後述:どれかで失敗しても処理を続ける) } ) } } ); } );

Slide 73

Slide 73 text

73 エラーハンドリングする

Slide 74

Slide 74 text

74 エラーハンドリング: どれかで失敗しても処理を続ける const promises = []; …(先述: 対応する関数を変数に入れる) promises.push( …(後述:エラー時に一定の時間をおいて繰り返す) ); const responses = await Promise.allSettled(promises); const fulfilledResponses = responses.filter((response) => {return response.status === "fulfilled";}); console.log(fulfilledResponses); const rejectedResponses = responses.filter((response) => {return response.status === "rejected";}); if (rejectedResponses.length > 0) { console.error(rejectedResponses); throw new Error(…); }

Slide 75

Slide 75 text

75 エラーハンドリング: エラー時に一定の時間をおいて繰り返す …(先述:どれかで失敗しても処理を続ける) promises.push(retry(deletionFunction, resource, 5)); … const retry = async (deletionFunction, resource, maxRetryAttempts) => { let attempt = 0; while (attempt < maxRetryAttempts) { try { return await deletionFunction(resource); } catch (error) { attempt++; if (attempt >= maxRetryAttempts) { throw error; } await new Promise(resolve => setTimeout(resolve, 1000)); } } };

Slide 76

Slide 76 text

76 • Resource Explorer APIの概要と利用方法 • タグ付けを用いたリソースの判別ロジック • 削除対象のものを判別 • 書式が沿っていないものを判別 • 削除関数の実装方法と使い方 • リソースタイプごとに実装すること • 自分を削除しないこと • テストの方法 • 自動テスト • 手動テスト 自分で実現できるようになるために(1/2)

Slide 77

Slide 77 text

77 • Deletion Policyタグをつけておく • 削除されないかつ不正フォーマットで通知されないように • 例えば? • YYYYMMを999912にする • Retainを入れておいてそれは不正フォーマットではないことにする 自分を削除しない

Slide 78

Slide 78 text

78 • Resource Explorer APIの概要と利用方法 • タグ付けを用いたリソースの判別ロジック • 削除対象のものを判別 • 書式が沿っていないものを判別 • 削除関数の実装方法と使い方 • リソースタイプごとに実装すること • 自分を削除しないこと • テストの方法 • 自動テスト • 手動テスト 自分で実現できるようになるために(1/2)

Slide 79

Slide 79 text

79 テスト対象 • 削除対象のものの判別関数 • 書式が沿っていないものの判別関数 • それらに必要な個別で切り出した関数 テスト方法 • Resource Explorer APIの返すJSON形式をもとに、入力と期待出力の JSONを作成 • ユニットテストでそれぞれの関数の返り値が期待出力に合っているか をassert 自動テスト

Slide 80

Slide 80 text

80 自動テストの例(用意するJSON: タグがないものの例) { "resources": [ { "Arn": "arn:example-not-having-any-properties-because-there-is-not-any-tags", "LastReportedAt": "2024-01-01T00:00:00.000Z", "OwningAccountId": ”012345678901", "Properties": [], "Region": "ap-northeast-1", "ResourceType": "example:example", "Service": "example" }, { "Arn": "arn:example-has-other-resource-data-but-no-deletion-policy-tag", … ] }

Slide 81

Slide 81 text

81 自動テストの例(実際のユニットテスト) describe('Test for separate-by-deletion-policy-havingness', function () { it('Deletion Policyキーの有無で判別し2つに分割できること', () => { const [ resourcesWithDeletionPolicyKey, resourcesWithoutDeletionPolicyKey ] = separateByDeletionPolicyHavingness( resourcesWithAllExamples.resources ); expect(resourcesWithDeletionPolicyKey).toEqual(expectedResourcesWithDeletionPolicyKe y.resources); expect(resourcesWithoutDeletionPolicyKey).toEqual(expectedResourcesWithoutDeletionP olicyKey.resources); }); });

Slide 82

Slide 82 text

82 • Resource Explorer APIの概要と利用方法 • タグ付けを用いたリソースの判別ロジック • 削除対象のものを判別 • 書式が沿っていないものを判別 • 削除関数の実装方法と使い方 • リソースタイプごとに実装すること • 自分を削除しないこと • テストの方法 • 自動テスト • 手動テスト 自分で実現できるようになるために(1/2)

Slide 83

Slide 83 text

83 テスト対象 • それぞれの削除関数全て テスト方法 • テストしたい関数で消す予定のリソースを作成 • if文でそのリソースのARN以外は即時returnし、該当リソースだけ削除 注意点 • 本番のAWSアカウントとは分けておくと事故がない • モックして自動テストすることも可能と思われるが今回は手動テスト の方が早くて確実だと判断した • 実装コストの観点 • モックではなく実際のAPIを使うことによる削除順依存・レート制限の再現 手動テスト

Slide 84

Slide 84 text

84 if文でそのリソースのARN以外は即時returnする例 export const deleteEc2Instance = async (resource) => { //追加 if (resource.Arn !== "arn:of-manually-created-resource") { return; } …(削除のコード) };

Slide 85

Slide 85 text

85 • 組織内で定める運用ルール • タグ付けの依頼 • 「何もしていない」は「削除して良い」とする • 削除する前に通知をする • 複数アカウントにまたがる場合の概要紹介 自分で実現できるようになるために(2/2)

Slide 86

Slide 86 text

86 タグ付けの依頼 • アカウントの利用時に読んでもらうドキュメントを作成 • そのドキュメントの中で依頼する • アカウントへの権限付与時にドキュメントを共有する 「何もしていない」は「削除して良い」とする • 削除して良い条件を書かせるのではなく、削除してはいけない場合に 必要事項を書かせる • そういうルールとしてドキュメントに記載する 削除する前に通知をする • 上2つに加えて通知することで、気づかなかったと言わせない • こちらの義務を果たした上で削除することで、納得を得られる 組織内で定める運用ルール

Slide 87

Slide 87 text

87 • 組織内で定める運用ルール • タグ付けの依頼 • 「何もしていない」は「削除して良い」とする • 削除する前に通知をする • 複数アカウントにまたがる場合の概要紹介 自分で実現できるようになるために(2/2)

Slide 88

Slide 88 text

88 • Control Towerにおける管理アカウントからメンバアカウント に対し設定の上書きをする例 • https://aws.amazon.com/jp/blogs/news/customize-aws-config-resource- tracking-in-aws-control-tower-environment/ 複数アカウントにまたがる場合の概要紹介

Slide 89

Slide 89 text

89 アカウントにおけるコスト削減を 不要なリソースの自動整理によって 自分で実現できるようになる 今日のゴール(ココマデ)

Slide 90

Slide 90 text

90 アカウントにおけるコスト削減を 不要なリソースの自動整理によって 自分で実現できるようになる 今日のゴール(改めて)

Slide 91

Slide 91 text

91 これで酒祭りで飲みまくる予算が確保できましたね!! 約 60 % 削減!

Slide 92

Slide 92 text

92 • X(旧Twitter)のDM • Xにて@メンション • 私の発表時間帯付近の #jawsfesta2024 #jawsfesta2024_c を付けた投稿 • できる限り全部追います!! • この後の休憩時間・会場で見かけたとき • ぜひ話しかけてください!! • 懇親会などなど全部参加します! • もちろんこちらでもぜひ!! ご質問・ご意見お待ちしております!! @amixedcolor エイミ

Slide 93

Slide 93 text

大志ある挑戦を創造し、日本から世界へ 想いを持った挑戦者と共に走り、共に創る