平凡な社会人の日記

平凡な社会人の日記です。怠惰な毎日を送っております。

【Nuxt3.6.5】Nuxt3でSPAのデフォルトのローディング画面を自作ページに変えたい

やりたいこと

HTMLやJSの取得、middlewareの実行中にNuxt3のデフォルトのローディング画面がでます。

個人開発だとこれはこれでかっこいいのですが、業務で使うとなるとNuxtで書かれていることがあまりにも分かりすぎますし、ユーザーには必要のない情報です。せっかく自分たちのコンポーネントでアプリケーションの統一感を出していても、このロード画面が出ることで統一感が損なわれUXが落ちる可能性があります。そこで、このロード画面を自作のページにしたいです。

結論

Nuxt3は ~/app/spa-loading-template.htmlを取ってきて表示しているだけなので、~/appディレクトリを作成し、そこにspa-loading-template.htmlを作成すればokです。

参考

nuxt.com

【Nuxt3.5.x】middlewareのなかでnavigateTo(path or url, {external: true})を使ってリダイレクトしようとすると見せたくないページが見えてしまう

これはなに?

Nuxt3にて、ページAからページBに遷移する際に、middlewareの中でnavigateToを使ってlocation.hrefまたはlocation.replaceでリダイレクトするとページBがちらっと見えてしまう現象の解決策を書きます。
発生した環境

結論

Nuxt3.6以上にしてください。

はじめに

こんにちは。最近はもっぱらフロントエンドばかり触っている3年目中途半端エンジニアのさっちゃんです。バックエンドとクラウドインフラ(AWS)もちょこちょこやります。猫が好きです。Twitter: @toshiakisan1127
さて、今回は自分が悩んだことを共有しておきます。簡単に言ってしまえばNuxt側の問題だったんですが、修正されたのが1ヶ月前と新しいこともありたどり着くまでに苦労しました。

前提:Nuxt3ではmiddlewareで、ユーザーが各ページに遷移できるかの認可をかけるのが一般的かと思います。そしてユーザーが閲覧権限を持っていなければ適切なページにnavigateToでリダイレクトしますが、ここではそのリダイレクト先のページが外部のサイトであることを想定して、navigateTo(path, {external: true}) でリダイレクトすることを考えます。

現象:indexページからSecretページに遷移する際にmiddlewareの中でリダイレクトされてログインページに遷移するが、その時にSecretページの内容が一瞬見えてしまう。

再現方法:
github.com
これをcloneしてきて、package.jsonにあるnuxtのversionを3.5.3にしてREADMEに書いてある手順を実行すれば再現できます。

調査

  • navigateToでリダイレクトするならそこで処理が終わって欲しいのに、実際は次のページのmountまで処理されている。
  • navigateToの後にsleepを入れれば白画面になり一旦問題ないが、テクニカルなのでやりたくない
  • navigateToの後にabortNavigationをすれば見せたくないページのチラ見せは無くなるが、今度は404のチラ見えが始まる。

navigateToのオプションやいろいろ試しましたが、結局このissueに辿り着きました。
github.com

やってることsetTimeoutとほぼ変わらんけど

> I like the idea of returning a never-resolving promise (return new Promise*1 for this case 👍

これ頭いいな。

これにより、次の動画のようになりました。



まとめ

Nuxtのアップデートは激しいのでissueちゃんと見よう。

## どうでもいいこと
はてなブログって動画を直接載っけられないんだな。

*1:) => {}

未経験からソフトウェアエンジニアになって2年が経ちました。

これはなに

開発未経験からソフトウェアエンジニアになって2年が経ったので、自分が何ができるのか棚卸ししておくだけの記事です。

目的

  • これからエンジニアになる人への道標
  • 何やっているかを聞かれた時に投げつける用
  • 転職の際に職務経歴書に貼る用(今のところ予定なし)

自己紹介

こんにちは。さっちゃん(Twitter:@toshiakisan1127)です。猫が好きです。
物理学科を卒業して大学院を中退して未経験でソフトウェアエンジニアになりました。詳しくは過去記事を見てもらえればと思います。

physics-heibon.hatenablog.com

あれから2年と数ヶ月、今のところ何とかやれています。今回は1年目と2年目を振り返りつつ、技術の棚卸しを行います。毎年やるのは、新卒で入った会社の先輩に、自分が何ができるか分からなくなるからやっておいた方がいいと言われてその通りだなと納得したからです。一年目は以下の記事に書いています。
physics-heibon.hatenablog.com

ちなみに、特に転職を考えているからというわけではないです。たまにカジュアル面談とかやってますが、それでも結局今の会社がいいと感じています。別に会社が好きではないのですが、部の人たちは好きですし給料も3年目にしては悪くはないと思ってます。働き方も柔軟。何より学ぶことが多くありますし周りもみんなそれぞれすごいポイントがあって刺激になります。もちろん思うことはなくはないですが、それはどこに行ってもあるものなのでokです。

1年目

大学院を中退して6月の終わりに就職活動を始め、新卒でIT企業のポテンシャル枠の採用に潜り込みました。ソフトウェアエンジニアになりたかったので入社まで少しでもプログラミングに慣れようと、9ヶ月くらいAtCoder競技プログラミングをやったり、基本情報技術者試験JavaScriptの勉強をしました。2.5ヶ月の研修では、プログラミングに触ったことがない、または少し触ったことがある人たちで、JavaScriptから始まってVue, Spring boot, PostgreSQL, Dockerを使ってローカルで動く認証認可付きのチャットアプリを作成しました。この時は自分はたまたまリードする方だったので業務時間中は周りや他のチームの手伝いに行って業務後2時くらいまで勉強や検証を行っていました。ある日は朝5時まで同期とやっていて、研修で教えてもらった内容の深掘りをやったりして楽しかったです。これが青春かと思いました。

7月からは新規開発のチームにアサインされたのですが、まず業務で書くコードの膨大さに驚いて、1ヶ月くらいは何が分からないかも分からない状態でした。しかし、先にエンジニアとして採用されていた同期や周りの人に恵まれてなんとか...なっていたかは怪しいです。
9月あたりからは新しいマイクロサービスをツヨツヨエンジニアと一緒に立ち上げることになったのですが、ここでAWSの話が会議に出てきてまた何も分からない状態になりました。VPCすらよく知らなかったですし、それなんですか?と聞いたらAWSの英語ドキュメントを渡されて読めといわれたので頑張って読んでました。休日もAWSの勉強をして、11月にはAWS SAAの資格を取ったあたりから設計にも口を出せるようになって楽しかったです。

この頃はSpring bootでAPIを建てたり、AWS SAM(CloudFormation)でIaC化したり、LambdaをPythonで書いてCognito、Neptune、DynamoDBで認証認可の機構を作ってました。Auth2.0の勉強も必要に応じてやりました。

2月あたりにはkubernetesの勉強を始めて業務でHelmを使い始めたり、SAMでデプロイしていたものの管理が辛くなってきたのでTerraformに全書き換えしました。特にkubernetes周りはめっちゃ強くて良い先輩と一緒にペアプロしていて、日曜日にも二人で出社して誰もいない会社で踊るなど一番楽しい期間でした。

4月あたりからはフロントエンドの人が足りないと言うことで自分もvueを書いてました。プロダクトオーナーと要件の認識を合わせてUIを考えて合意を取ってから実装、バックエンドとの疎通、stgテストを行いました。これを7月まで続け、転職することになります。(転職理由については直接会って聞いてください、大体家庭の事情です。)

2年目

8月からは心機一転、新しい会社でtoBの大規模開発に参画しました。すでに出来ている文化にはなかなか馴染めず、最初の3ヶ月くらいは毎日つらいつらいと言っていましたが、この辛さは自分が選んだものであり、この辛さが自分の成長のためには必要だと思っていたので感情とは裏腹に計画通りだなーと客観的に考えていました。最初はフロントエンドの簡単な開発から入りましたが、大規模なシステムのコードを見るのは初めてだったのでまた途方にくれることになりました。しかし、前職ですぐに泣きつく重要さを理解していたので何も出来ずに時間が過ぎるというようなことはなかったように思います。会議で出た分からない用語は全部メモして、適当な人の時間を確保して壁打ちしてました。
そんなこんなで引き続きVue、Spring bootを中心に案件をこなしながら最近まで生きています。大規模開発ならではの事例として、大量のレコードを捌く時に適当にやっているとO(N^2)になってしまうところをO(NlogN)にするなど、競技プログラミングで培ったパフォーマンスを気にする実装が出来て興奮しました。「月刊 競技プログラミングは役に立つのか」A. 俺には役に立った!

やっていることとしては基本的に、企画の方がやりたいことに対して何のためにやるのか、どういう手段でやるのか、小さく早くユーザーに価値を届けるにはどうすればいいかを一緒に考えて、概要の設計をして見積もってGOが出れば詳細に設計したり実装、テスト、リリースまで行っています。
そんなこんなで最近は品質を保つためにチーム内で学習コンテンツを用意して半年に一度やってもらったり、パフォーマンス改善部隊としてパフォーマンスを測定する環境を整えて改善し、よくない実装が入ったら修正の提案をしたり楽しくやってます。また、Nuxt3も業務で触るようになりましたが、ルールがあってvueに比べて好き勝手かけないところが自分は大好きです。個人開発でもNuxt3 + app runnerを使っています。

技術

()はチームでは使っていたが自分は触っていないのであまり自信がない。思いついた順です。

  • フロントエンド
    • Vue2/3, Nuxt3
    • JavaScript, TypeScript
    • Vuex, Pinia
    • jest, MSW
    • axios
    • webpack, vite
  • バックエンド
  • インフラ
    • Docker
    • AWS
      • EKS/ECS
      • RDS
      • S3
      • Lambda
      • DynamoDB
      • Code シリーズ
      • Neptune
      • CloudFormation
      • CloudFront
      • あとなんか色々(面倒になった)

終わりに

AWS DOPと英語を勉強中!まったりがんばろ〜。

grinem_python チートシート

これはなに?

gremlin_pythoについての知見が自分の中で溜まってきたので、共有も兼ねてここに残しておきます。

目標

gremlin python で基本的なクエリの書き方が分からない方が雰囲気で書けるようになる

こんにちは

ソフトウェアエンジニア一年生のさっちゃん(twitter: @toshiakisan1127)です。普段はWebアプリケーションのバックエンドをSpring boot, Pythonを使って書いたり、CloudFormationやTerraformでAWS上のリソースを管理をしています。好きなAWSのサービスはLambdaです。

はじめに

この記事を見る方はgremlin pythonについてはご存知だと思いますので、グラフDBやgremlin python自体の説明は省きます。知らない方は Amazon Neptune 触ってみた。~グラフDB楽しい。~ を見てもらえるとありがたいです。 また、Neptuneを使っている方はjupyter notebookからグラフを操作することもあると思いますが、その際にgremlin_pythonと書き方が少し違ったりするので、gremlin_pythonでの書き方と同時にjupyter_notebookでの書き方も一緒に載せておきます。

version

gremlinpython==3.5.2 python3.8

グラフDBとの接続

Lambdaなどから接続できない場合はセキュリティグループの設定やプライベートサブネットのルートテーブルのデフォルトルートがNATゲートウェイに向いているかを確認してください。

from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection
from gremlin_python.process.anonymous_traversal import traversal

g = traversal().withRemote(DriverRemomteConnection("wss://(endpoint_name):(port_number)/gremlin','g'"))

Neptuneのマネジメントコンソールから開いた場合は, %%gremlin をセルの最初に書けば g というのが GraphTraversalSourceオブジェクトとして認識されます。

書き込み系

頂点を追加する

gremlinpython

g.addV("cat").toList() # Labelが cat の頂点ができる。戻り値はvertex object のリスト: [ v[abcd-12e...] ] .

Neptune jupyter notebook

%%gremlin
g.addV("cat")

頂点を作成する時にpropertyを追加する

gremlinpython

%%gremlin
g.addV("cat").property("name", "Tama").toList() # idがuuid, Labelが cat , name property が Tama の頂点ができる。

Neptune jupyter notebook

%%gremlin
g.addV("cat")..property("name", "Tama")

頂点のpropertyを変更する

gremlinpython

from gremlin_python.process.traversal import Cardinality

cat_vertex = g.addV("cat").property(Cardinality.single, "name", "Tama").toList()[0] # idがuuid, Labelが cat , name property が Tama の頂点ができる。
g.V(cat_vertex).property(Cardinality.single, "name", "Pochi").toList()

Neptune jupyter notebook

%%gremlin
g.addV("cat").property("name", "Tama")
%%gremlin
g.V().has(id, "cat_vertex_id").property(single, "name", "Pochi")

辺を追加する

from gremlin_python.process.traversal import  Cardinality

cat_vertex = g.addV("cat").property(Cardinality.single, "name", "Tama").toList()[0]
food_vertex = g.addV("food").toList()[0]

g.addE('love').from_(cat_vertex).to(food_vertex).toList()

Neptune jupyter notebook

%%gremlin
g.addE("love").from(g.V().has(id, "vertex1_id")).to(g.V().has(id, "vertex2_id"))

辺にpropertyを追加する

from gremlin_python.process.traversal import Cardinality

cat_vertex = g.addV("cat").property(Cardinality.single, "name", "Tama").toList()[0]
food_vertex = g.addV("food").toList()[0]

g.addE('love').property("hoge", "fuga").from_(cat_vertex).to(food_vertex).toList()

Neptune jupyter notebook

%%gremlin
g.addE("love").property("hoge", "fuga").from(g.V().has(id, "vertex1_id")).to(g.V().has(id, "vertex2_id"))

読み込み系

特定のIDを持つ頂点を取得する

from gremlin_python.process.traversal import T
cat_vertex_list = g.V().has(T.id, "<vertex_id>").toList()

Neptune jupyter notebook

%%gremlin
g.V().has(id, "<vertex_id>")

特定のLabelを持つ頂点を取得する

cat_vertex_list = g.V().hasLabel("cat").toList()

Neptune jupyter notebook

%%gremlin
g.V("cat")

特定のpropertyを持つ頂点を取得する

cat_vertex_list = g.V().has("property_1", "hoge").toList()

Neptune jupyter notebook

%%gremlin
cat_vertex = g.V().has("property_1", "hoge")

特定のIDを持つ辺を取得する

from gremlin_python.process.traversal import T
edge_list = g.E().has(T.id, "<edge_id>").toList()

Neptune jupyter notebook

%%gremlin
edge = g.E().has(T.id, "<edge_id>")

特定のLabelを持つ辺を取得する

edge_list = g.E().hasLabel("some_label").toList()

Neptune jupyter notebook

%%gremlin
edge = g.E("some_label")

特定のpropertyを持つ辺を取得する

edge_list = g.E().has("property_1", "hoge").toList()

Neptune jupyter notebook

%%gremlin
edge = g.E().has("property_1", "hoge")

特定の頂点から伸びる辺を取得する

from gremlin_python.process.traversal import T
edge_list = g.V().has(T.id, "some_vertex_id").outE().toList()

Neptune jupyter notebook

%%gremlin
edge = g.V().has(id, "some_vertex_id").outE()

特定の頂点から伸びる辺を取得する(Labelで絞る)

from gremlin_python.process.traversal import T
edge_list = g.V().has(T.id, "some_vertex_id").outE("some_edge_label").toList()

Neptune jupyter notebook

%%gremlin
edge = g.V().has(id, "some_vertex_id").outE("some_edge_label")

特定の頂点に入る辺を取得する

from gremlin_python.process.traversal import T
edge_list = g.V().has(T.id, "some_vertex_id").inE().toList()

Neptune jupyter notebook

%%gremlin
edge = g.V().has(id, "some_vertex_id").inE()

特定の頂点に入る辺を取得する(Labelで絞る)

from gremlin_python.process.traversal import T
edge_list = g.V().has(T.id, "some_vertex_id").inE("some_edge_label").toList()

Neptune jupyter notebook

%%gremlin
edge = g.V().has(id, "some_vertex_id").inE("some_edge_label")

特定の頂点に関する辺を向きに関係なく取得する

from gremlin_python.process.traversal import T
edge_list = g.V().has(T.id, "some_vertex_id").out

Neptune jupyter notebook

%%gremlin
edge = g.V().has(id, "some_vertex_id").both("some_edge_label")

特定の頂点から別の頂点へ伸びる辺を取得する

from gremlin_python.process.traversal import T
edge_list = g.V().has(T.id, "some_vertex_id").outE("some_edge_label").as_('e').inV().hasLabel('vertex_label').select('e').toList()

Neptune jupyter notebook

%%gremlin
edge = g.V().has(id, "some_vertex_id").outE("some_edge_label").as_('e').inV().hasLabel('vertex_label').select('e')

辺を無向辺だとみなしたときに連結な頂点の中から特定の頂点を探す

from gremlin_python.process.traversal import T
 #dedup().by(T.id) でidが重複している格納しているリストの中から消す。これによって無限ループにならない。
vertex_list = g.V().has(T.id, "some_vertex_id") \
    .repeat(__.both().dedup().by(T.id) \ 
    .until(__.has(T.id, "some_vertex2_id")) \
    .toList()

Neptune jupyter notebook

%%gremlin
edge = g.V().has(id, "some_vertex_id") \
    .repeat(__both()).dedup().by(id))\
    .until(__.has(id, "some_vertex2_id")) \
    .toList()

他の小技

jupyter nootbook上でグラフをいい感じに表示する

%%gremlin -p v,oute, inv
g.V().outE().inV().path().by(elementMap())

備考

使うサーバーによってはidがuuidだったりintegerだったりするので注意。

終わりに

案外多かった...。他にもあれば追加します。誰かの役に立てば幸いです。

参考

terraform destroy したときにサブネットがなかなか消えないときは何かリソースが乗ってるかも

これはなに?

terraform に関するちょっとしたtipsです。自分が20分程度こまったので、共有しておきます。

結論

タイトルの通りです。

こんにちは

さっちゃん(@toshiakisan1127)です。ソフトウェアエンジニア1年生で、普段はWebアプリケーションの開発でバックエンド(with Python, Spring boot)と、インフラ(with CloudFormation, terraform, kubenetes)をやってます。

現象

先日、開発環境のアーキテクチャを変更するために、サブネット上に乗っているk8sクラスターをdeleteして、インフラ部分を

terraform destroy 

しようとすると、あるサブネットだけ待てど暮らせどdestroyされず気づいたらおじいちゃんになってしまいました。(実際は20分くらい待った。)しかしエラーにはならないので、強制終了するとtfstateが壊れそうなので踊るしかやることがなかったです。

調査

まずはマネジメントコンソールから直接サブネットを壊そうとすると下の画像のように、「ネットワークインターフェースが残っている」と言われて消せませんでした。

f:id:physics-heibon:20220326180538p:plain
サブネットを消そうとするとネットワークインターフェースが残っていると言われる。なんだそりゃ?

これはあるEC2についてるみたいなので、(あれ、EKSクラスター落としたはずだけどなぁ...。)とか思いながらマネコン上をうろうろしてました。

この時点で「何か上に残ってるんでは?」と仮説を立ててましたが、サブネットの上に何が乗っているかを探すのってマネジメントコンソール上では辛いです...。なにかいい方法があったりするんでしょうか。今回は各マイクロサービスのterraformやcloudformationを見て回ることで、該当のサブネットを使ってそうなものがないか探して回りました。(手でリソース作ってたらどーすんねん。)

原因

結局、他のマイクロサービスのLambdaがそのサブネットの上に乗っていたので、そのアプリケーションもdestroyすると一瞬でサブネットが消えました。上記の残っていたネットワークインターフェースはLambdaの実行環境のEC2にアタッチされているものみたいです。>

f:id:physics-heibon:20220326181548p:plain
アタッチされているインスタンスの所有者がAWSになっている。Lambdaの実行環境の責務がAWS側にあるという話かねぇ。

教訓

  • サブネットのdestroyは、上にリソースが乗っている場合にエラーではなくTimeoutになる。(本題)
  • Lambdaの実行環境ってEC2の上にあるのかぁ。そりゃそうかもしれないが、裏側が見えた気がしてちょっと面白い(雑談)

とある未経験ITエンジニア、一年目が終わる。

これはなに?

IT業界未経験の自分がエンジニアとして一年間でどれだけ成長できたかを書き留めておくだけです。IT業界に未経験で入る方の参考になればと思います。

結論

やったことのない仕事でも報告・連絡・相談をこまめに行いながら調査、実験することである程度の成果物が安定して出せるようになったことが一番大きいと思います。また、2,3人のタスクを割り出し、共有、管理をやることもあります。

自己紹介

こんにちは。猫好きITエンジニャーのさっちゃん(@toshiakisan1127)です。今回は新卒でIT企業に入社した自分が、研修、仕事を通して何ができるようになったかをまとめようと思います。正直まだまだ「ITエンジニアです」と胸を張れるほどやれていないので私生活では「パソコンをぽちぽちする仕事をしています。」と言っていますが、ここではITエンジニアと言っておきます。ソフトウェアエンジニアと言った方が正確かもしれません。現在の技術スタックは最後にまとめておきます。

入社前にやっていたこと

自分は大学と大学院では物理学をやっていたのですが、さまざまな事が重なって大学院を中退してあるIT企業に入社しました。(中退した理由はこちら)大学院の後期は休学していたため時間があったので、少しでもIT系の知識をつけようと思い以下のことを行いました。

  1. 競技プログラミングAtCoder
  2. progate
  3. 基本情報技術者試験
  4. javascriptのお勉強
  5. Linux 入門

以下捕捉。 1: 最初はプログラミングの勉強に目的が欲しくて5月ごろに始めましたが、ハマってしまって9,10,11月はずっとやっていました。言語はPythonで、このブログにも記事を載せています。レートは730くらいです。 2: いろんな言語に触ってみたくて、月1000円くらいだったので8月中にHTML, CSS初級・中級、javascript, python, Go, Ruby, スクリプトあたりをやって退会しました。 3: 薄く広く知識をつけたかったので基本情報技術者試験の勉強をしました。合格点は取れたんですが、選択問題をチェックするフラグをつけ忘れて取れませんでした...。ただここで身につけた知識は入社後に役立ちました。(というかこれくらいの知識がないとお話にならないことが多くて危なかった...。) 4: javascript本格入門という本を読みながら言語の使用を理解しようとしていました。しかしあまり身に付かなかったように思います。 5: 新しいLinuxの教科書という本でポチポチ練習しました。これもやっておいてよかったです。

他にもdockerだったりAWSでEC2のインスタンスを立ててsshでログインしてみたりと色々やりましたが、入社後ほどは身に付かなかったです。やっぱり最初の一歩は理解している人に教えてもらいながら進めた方が圧倒的に効率がいいように思います。この経験から、実務でも最初はペアプロ・モブプロを積極的に行なっています。

研修編

自分はエンジニアではなく(エンジニアになる前提で)総合職の枠で入ったので、初めはIT未経験の方々と一緒に2.5ヶ月間研修を受けさせてもらえました。バニラなjavascriptで簡単な呟きアプリを作成するところから始まりましたが、自分は早く終わったので、躓いているチームメンバーに困っていることはないか聞きつつTypeScriptとvue.jsの勉強をしていました。研修の後半ではvue, Spring boot, postgresqlを使ってログイン機能がついたモノリスなチャットアプリを作成しました。チーム開発を通してgitの使い方やタスクの管理を学べましたが、同期と仲良くなれたのが一番良かったかなと思います。dockerで環境を作ること、Spring bootの環境構築に苦労したことを覚えています。班の中では自分が割とできるほうだったので、業務中は班の進捗を管理したりゴールを設定しつつ、夜中まで環境構築したりSpring bootの勉強をして班員に教えられるように勉強していました。(時には日付を超えて勉強していて翌日寝坊してました。)全てが新鮮で「楽しいな〜」と言いながらやってました。

配属後

6月中旬に12人程度の新規開発チームに配属されました。業務知識、アジャイルスクラム、DDD、オニオンアーキテクチャ、マイクロサービスアーキテクチャなど勉強することが多く、またチームメンバーのことも知らないので「いつでも質問してね」と言われても何を質問すればいいか分からなかった最初の1ヶ月は辛い時期でした。今思うと、質問ではなく「不安」を共有するようにすればよかったですし、今は自分が教える時は「何か質問はありますか?」の後で「何か不安に思っていることはありますか?」と聞くようにしています。7月、8月はSpring bootでAPIの設計・実装・自動テストをしていました。

9月から新しいマイクロサービスの立ち上げを手伝いました。ここで初めてAWSを本格的に触るようになりましたが、API Gateway, Lambda, Cognito, Neptuneなど聞いたこともないサービスもOAuth2.0も分からなくて非常に困りました。会議をしても何を言っているかわかりません。AWSアーキテクチャのレビュー会議でも、それがいいのか悪いのかを判断する基準がなくて、本当に何も分からん!という感じでした。しかし分からん分からん言ってても分かるようにはならないので、業務後や土日を使ってAWS SAAの勉強を始め、11月に取得しました。さらに業務で話に出たサービスを個人でも遊んでみることで理解を深めました。OAuth2.0も趣味で勉強しました。(というかしないと文字通りお話にならない...。)

また、手でLambdaをデプロイするのは厳しいので、9月に初めてIaCの一種としてAWS SAMに手を出しました。SAMについてはこちらを書きました。SAMはCloudformationのラッパーですが、AWSのドキュメントとの睨めっこが厳しかったです...。CI/CDの構築が楽なのはいいところです。

11月後半あたりからterraformも触りましたが、SAMを触ってからterraformを触ると、ドキュメントが整備されていてめちゃくちゃ楽しかったです。

Python についてはLambdaで使っていますし、コルーチンを使ってシングルスレッドで並列処理を行う方法なども勉強しました。競技プログラミングでは使わない技術なので楽しいです。

そんなこんなでAPIの設計・実装・テスト、インフラ周りの設計・構築、アーキテクチャレビューを主に業務として行っていると3月になりました。kubernetesもjobを書いてapplyしたりしましたが、ちゃんと分かってないので近いうちに勉強します。

最近はterraformでgitlab runnerを作って社内記事にしたりして遊んでます。

これから

あっというまに1年が過ぎようとしています。よく成長したなと思う反面、もっとうまくやれたなーという思いです。これからですが、まだまだ勉強したいことがあるのでそれを消化していきます。転職を考えたりもしていますが、自分は今のチームが好きで学べることが多く、何より楽しくやれているのでもう少し先かなーと考えています。(会社は「どちらかというと好き」くらい。)でもいい条件があれば会社を変えることも考えています。

技術スタック

(技術スタック書くの難しい...。)

業務で使ったことがあるAWSのサービス

IAM, EC2, VPC, EKS, Aurora for mysql, API Gateway, Lambda, DynamoDB, Cognito, Neptune, Cloudformation, Cloudwatch logs, x-ray, Route53, S3, KMS, SAM

触ったことがあるAWSのサービス(遊び含む)

ElasticBeanStalk, ECS, Fargate

資格

AWS SAA

他にも思い出したら追記しておきます。

最後に

自分に対する自分の評価としては、すごく良くできるわけではないですが一年目はこんなもんじゃないですか?知らんけど。

【AWS で遊ぶ】 AWS Elastic Beanstalk でインフラのことを気にせずに簡単デプロイ!

 本稿の対象者

  • AWS でとりあえずアプリを動かしたい
  • 小さく作ってみたい
  • AWS ElasticBeanstalkについて知りたい

本稿の目標

  • AWS Elastic Beanstalk を使って Flask で書かれたアプリケーションをデプロイしてみる
  • AWS Elastic Beanstalk が何をしてくれるかを知る

はじめに

こんにちは。新卒 1 年目エンジニアのさっちゃんです。普段は Spring boot で API の設計・実装をしたり、AWS 上で Python で Lambda 書いたりしてます。使っている AWS のサービスは主に API Gateway, Lambda, Cognito, Neptune, Dynamo, S3 などで、最近は CloudFormation を使ってインフラをコード化することにハマってます。なんでもコードで管理したいお年頃。

さて、最近趣味で AWS ソリューションアーキテクト アソシエイトの勉強をしていると、さまざまなサービスが出てくるので遊んでみたくなります。その中でも「アプリケーションを実行しているインフラストラクチャについて深く意識していなくても、アプリケーションのデプロイとその管理を簡単に行うことができます。」(徹底攻略 AWS 認定ソリューションアーキテクト -アソシエイト教科書 p.90)と紹介されていた AWS Elastic Beanstalk を使えばどれくらい簡単にデプロイできるものなのか、何をしてくれるのか気になったので実際に使ってみました。

AWS Elastic Beanstalk とは

まずは AWS Elastic Beanstalk とはなんなのか、いつも通り公式サイトを見てみましょう。

AWS Elastic Beanstalk は、Java、.NET、PHP、Node.js、PythonRuby、Go および Docker を使用して開発されたウェブアプリケーションやサービスを、Apache、Nginx、Passenger、IIS など使い慣れたサーバーでデプロイおよびスケーリングするための、使いやすいサービスです。

お客様はコードをアップロードするだけで、Elastic Beanstalk が、キャパシティのプロビジョニング、ロードバランシング、Auto Scaling からアプリケーションのヘルスモニタリングまで、デプロイを自動的に処理します。同時に、お客様のアプリケーションが稼動している AWS リソースの完全な制御を維持でき、いつでも基盤となるリソースにアクセスすることができます。

Elastic Beanstalk には追加料金はかかりません。アプリケーションを格納および実行するために必要な AWS のリソースに対してのみお支払いいただきます。

なるほど分からん。とにかく、コードをアップロードすればデプロイやロードバランシング、ヘルスモニタリングをいい感じにやってくれるらしいです。まぁとにかくやってみるかー。

まずはサンプルをデプロイ

まぁとにかく手を動かしてみるのがいいですね。コンソールから Elastic Beanstalk を選択してアプリケーションを作成します。名前は、このアプリを作成することは私にとって偉大な一歩なので GreatApplication にします。

f:id:physics-heibon:20211031012959p:plain
偉大な一歩

言語は Python を選択します。

f:id:physics-heibon:20211031013025p:plain

1 分くらい待つと次のような画面が出ます。なにかよくわかりませんがデプロイされたようです。

f:id:physics-heibon:20211031013106p:plain
がちょんがちょん...。

f:id:physics-heibon:20211031013128p:plain
なんかできた。

greatapplication-env.eba-ruyxxcr5.ap-northeast-1.elasticbeanstalk.com をブラウザで見ると次のように、Your first AWS Elastic Beanstalk Python Application is now running on your own dedicated environment in the AWS Cloud とデフォルト画面が出ます。

f:id:physics-heibon:20211031013148p:plain
おー。実感が湧かないけどデプロイできたみたい。

しかし自分はコードを何も見ていません。ソースコードはどこにあるんでしょうか?チュートリアルおよびサンプルPython のサンプルをダウンロードしてみると発見できました。どうやらデフォルトではこちらが適用されるようです。

import logging.handlers

# Create logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# Handler
LOG_FILE = '/tmp/sample-app.log'
handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=1048576, backupCount=5)
handler.setLevel(logging.INFO)

# Formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# Add Formatter to Handler
handler.setFormatter(formatter)

# add Handler to Logger
logger.addHandler(handler)

welcome = """
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <!--
    Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.

    Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at

        http://aws.Amazon/apache2.0/

    or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
  -->
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Welcome</title>
  <style>
  body {
    color: #ffffff;
    background-color: #E0E0E0;
    font-family: Arial, sans-serif;
    font-size:14px;
    -moz-transition-property: text-shadow;
    -moz-transition-duration: 4s;
    -webkit-transition-property: text-shadow;
    -webkit-transition-duration: 4s;
    text-shadow: none;
  }
  body.blurry {
    -moz-transition-property: text-shadow;
    -moz-transition-duration: 4s;
    -webkit-transition-property: text-shadow;
    -webkit-transition-duration: 4s;
    text-shadow: #fff 0px 0px 25px;
  }
  a {
    color: #0188cc;
  }
  .textColumn, .linksColumn {
    padding: 2em;
  }
  .textColumn {
    position: absolute;
    top: 0px;
    right: 50%;
    bottom: 0px;
    left: 0px;

    text-align: right;
    padding-top: 11em;
    background-color: #1BA86D;
    background-image: -moz-radial-gradient(left top, circle, #6AF9BD 0%, #00B386 60%);
    background-image: -webkit-gradient(radial, 0 0, 1, 0 0, 500, from(#6AF9BD), to(#00B386));
  }
  .textColumn p {
    width: 75%;
    float:right;
  }
  .linksColumn {
    position: absolute;
    top:0px;
    right: 0px;
    bottom: 0px;
    left: 50%;

    background-color: #E0E0E0;
  }

  h1 {
    font-size: 500%;
    font-weight: normal;
    margin-bottom: 0em;
  }
  h2 {
    font-size: 200%;
    font-weight: normal;
    margin-bottom: 0em;
  }
  ul {
    padding-left: 1em;
    margin: 0px;
  }
  li {
    margin: 1em 0em;
  }
  </style>
</head>
<body id="sample">
  <div class="textColumn">
    <h1>Congratulations</h1>
    <p>Your first AWS Elastic Beanstalk Python Application is now running on your own dedicated environment in the AWS Cloud</p>
    <p>This environment is launched with Elastic Beanstalk Python Platform</p>
  </div>

  <div class="linksColumn">
    <h2>What's Next?</h2>
    <ul>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/">AWS Elastic Beanstalk overview</a></li>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/index.html?concepts.html">AWS Elastic Beanstalk concepts</a></li>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/create_deploy_Python_django.html">Deploy a Django Application to AWS Elastic Beanstalk</a></li>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/create_deploy_Python_flask.html">Deploy a Flask Application to AWS Elastic Beanstalk</a></li>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/create_deploy_Python_custom_container.html">Customizing and Configuring a Python Container</a></li>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/using-features.loggingS3.title.html">Working with Logs</a></li>

    </ul>
  </div>
</body>
</html>
"""


def application(environ, start_response):
    path = environ['PATH_INFO']
    method = environ['REQUEST_METHOD']
    if method == 'POST':
        try:
            if path == '/':
                request_body_size = int(environ['CONTENT_LENGTH'])
                request_body = environ['wsgi.input'].read(request_body_size)
                logger.info("Received message: %s" % request_body)
            elif path == '/scheduled':
                logger.info("Received task %s scheduled at %s", environ['HTTP_X_AWS_SQSD_TASKNAME'],
                            environ['HTTP_X_AWS_SQSD_SCHEDULED_AT'])
        except (TypeError, ValueError):
            logger.warning('Error retrieving request body for async work.')
        response = ''
    else:
        response = welcome
    start_response("200 OK", [
        ("Content-Type", "text/html"),
        ("Content-Length", str(len(response)))
    ])
    return [bytes(response, 'utf-8')]

では template の部分を変えてみてデプロイの練習をしてみます。

import logging.handlers

# Create logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# Handler
LOG_FILE = '/tmp/sample-app.log'
handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=1048576, backupCount=5)
handler.setLevel(logging.INFO)

# Formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# Add Formatter to Handler
handler.setFormatter(formatter)

# add Handler to Logger
logger.addHandler(handler)

welcome = """
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Welcome</title>
  <style>
  body {
    color: #ffffff;
    background-color: #E0E0E0;
    font-family: Arial, sans-serif;
    font-size:14px;
    -moz-transition-property: text-shadow;
    -moz-transition-duration: 4s;
    -webkit-transition-property: text-shadow;
    -webkit-transition-duration: 4s;
    text-shadow: none;
  }
  h1 {
    font-size: 500%;
    font-weight: normal;
    margin-bottom: 0em;
  }
  </style>
</head>
<body>
  <h1>
    猫は偉大。
  </h1>
</body>
</html>
"""


def application(environ, start_response):
    path = environ['PATH_INFO']
    method = environ['REQUEST_METHOD']
    if method == 'POST':
        try:
            if path == '/':
                request_body_size = int(environ['CONTENT_LENGTH'])
                request_body = environ['wsgi.input'].read(request_body_size)
                logger.info("Received message: %s" % request_body)
            elif path == '/scheduled':
                logger.info("Received task %s scheduled at %s", environ['HTTP_X_AWS_SQSD_TASKNAME'],
                            environ['HTTP_X_AWS_SQSD_SCHEDULED_AT'])
        except (TypeError, ValueError):
            logger.warning('Error retrieving request body for async work.')
        response = ''
    else:
        response = welcome
    start_response("200 OK", [
        ("Content-Type", "text/html"),
        ("Content-Length", str(len(response)))
    ])
    return [bytes(response, 'utf-8')]

書き換えてzip -r sample.zip ./などで zip に固めて「アップロードとデプロイ」からファイルを選択します。そしてもう一度 Greatapplication-env.eba-ruyxxcr5.ap-northeast-1.elasticbeanstalk.com を見てみると、ちゃんと「猫は偉大。」と表示されています。なるほど、コードをアップロードするだけで勝手にデプロイされているらしい。なんかよく分からんけど動くからヨシッ!

f:id:physics-heibon:20211031013242p:plain

もう満足したので環境を削除します。次のように削除は簡単です。

f:id:physics-heibon:20211031013303p:plain

Flask で書かれたアプリケーションをデプロイ

Python だけだと味気ないので Flask で書かれたアプリケーションでもデプロイしてみましょうかね。Flask なんも知らんけど。先程と同様に、アプリケーション名は GreatFlaskApplication として(自分が作るものはなんでも偉大なので)、プラットフォームは Python にします。また、先ほどはアプリケーションコードとして「サンプルアプリケーション」を選択しましたが、今回は「コードのアップロード」を選択します。また、コードについては Deploying a Flask App to AWS Elastic Beanstalk を参考に、次のようなディレクトリ構成をとることにします。

.
|--- .ebextensions
|       |--- app.config
|--- application.py
|--- requirements.txt

中身は次の通りです。

app.config

files:
  "/etc/httpd/conf.d/wsgi_custom.conf":
    mode: "000644"
    owner: root
    group: root
    content: WSGIApplicationGroup %{GLOBAL}

requirements.txt

Flask==1.1.1

application.py

from flask import Flask
application = Flask(__name__)

@application.route("/")
def index():
    return "Your Flask App Works!"

@application.route("/hello")
def hello():
    return "Hello World!"


if __name__ == "__main__":
    application.run(port=5000, debug=True)

これらを zip に固めて「ソースコード元」としてアップロードします。これだけでいいのですが、今回は「より多くのオプションを設定」を選んでどんな設定があるのか見てみましょう。

f:id:physics-heibon:20211031013423p:plain

f:id:physics-heibon:20211031013442p:plain

f:id:physics-heibon:20211031013456p:plain

f:id:physics-heibon:20211031013510p:plain

f:id:physics-heibon:20211031013534p:plain

他にも設定がありますが、そのままで使います。

f:id:physics-heibon:20211031013609p:plain

がちょんがちょん...。CloudWatch アラームだったり Auto Scaling group、EC2 インスタンス、セキュリティグループ、S3 バケットが作成されているみたいです。

f:id:physics-heibon:20211031013626p:plain

できたみたいです。ブラウザで見てみます。

f:id:physics-heibon:20211031013643p:plain

やったー動いた ✌️

自動で作られた AWS サービスを眺める

なるほど、確かに簡単にアプリケーションをデプロイできることは確認できました。しかし、何が作られたのか分からないままではかかる料金も分からないので気になります。新卒はお金がないので、AWS を触る時は料金に敏感です。

コンソール上から色々みると、大体「ウェブサーバー環境」のようになっているみたいです。(Route53 は作成されてません)

f:id:physics-heibon:20211031013656j:plain

また、サブネットやインターネットゲートウェイのアタッチ、ルーティングなど様々なことをやってくれているみたいです。勉強になってありがたい。

最後に

今回は AWS Elastic Beanstalk について、実際にアプリケーションをデプロイする事と自動作成されたサービスを眺めることを目標に書きました。SAA の勉強で見たことが多かったので、「これ進 ◯ ゼミで見たやつ!!」という状態で楽しかったです。次回は EKS か Lambda Layer について書く予定ですが、気まぐれなので好きなことを書きます。

今回に限らず、間違った認識をしていれば指摘してください。勉強になるので。

関係ないですが、パフェが食べたいです。

参考

AWS Elastic Beanstalk

徹底攻略 AWS 認定ソリューションアーキテクト -アソシエイト教科書

チュートリアルおよびサンプル

Deploying a Flask App to AWS Elastic Beanstalk

AWS Elastic Beanstalk デベロッパーガイド ウェブサーバー環境