TECH PLAY

株式会社ラクス

株式会社ラクス の技術ブログ

927

皆さんこんにちは。インフラエンジニアやってますkumakichi_kunです。 ついこの間、、、かと思いましたがそうでもないですね、、、 数年前に業務で Ansible Vault を導入しようとした際に私自身すごく困ったことがあるので、 コマンド例や注意ポイントなどをご紹介いたします。 Ansible Vaultってなに? ここから本題:Ansible Vaultの紹介 1. 1つのファイル丸ごと 2. 1つの変数のみ memo)Ansible Vaultの歴史というか流れのようなもの まとめ 暗号化の単位(というか範囲)は2パターンあるよ! それぞれメリットとデメリットがあるよ! 結論)暗号化する対象を見極めて、 1つのファイル丸ごとと1つの変数のみの形式をうまく使い分けようね! おわりに ちょっとした小技集(蛇足あるいは自分用のメモ) Ansible Vaultってなに? Ansible内で平文のまま扱いたくない情報を暗号化する機能 です。(Ansibleバージョン:v1.5以降?) ファイルの実体としては以下のような UTF-8 で エンコード されたテキストですが、 $ANSIBLE_VAULT;1.2;AES256;test 64623535646465656235363633623431396365666237653663633962616437663239343762356463 3833333531663736656533643734303763393835383663650a386231626230373937666331306536 36613136303934643733346566346366653434643039626535646637353230663839353234363039 6364353566643030620a323733616636666131656263373336383233303636376339316634363061 3262 ansible-playbook コマンドなどを実行する際には内部で以下のように認識される仕組みになります。 this_is_txt 公式ドキュメント(日本語版) 補足①:本記事でのansible実行環境の情報 (バージョンの古さはスルーしていただけると...) $ ansible --version ansible 2 . 7 . 10 補足②:なんでAnsible Vaultが必要だったの? Ansibleのroot ディレクト リをGit上の1PJと紐づけて、バージョン/履歴管理を行っており、 Ansible内で平文のまま扱われる=Git上で見えてしまう⇒公開される範囲が適切じゃない という課題が当時存在しました(PJの公開範囲が問題じゃないか、というのは置いておいて、、、)。 これが要因でパスワードの管理をAnsibleとは別のところでやっていたってのが、当時一番解消したいことでした。 これを解決するためAnsible Vault導入を始めました。 ここから本題:Ansible Vaultの紹介 Ansible Vaultでの暗号化は以下2つのパターンがあります。 1つのファイル丸ごと 1つの変数のみ それぞれについてコマンドの実行例などとともにご紹介していきます。 1. 1つのファイル丸ごと 1つのファイルの中身をまるっと暗号化する形式です。(そのまんまですねw) 暗号化されてる状態 $ANSIBLE_VAULT;1.2;AES256;test 64623535646465656235363633623431396365666237653663633962616437663239343762356463 3833333531663736656533643734303763393835383663650a386231626230373937666331306536 36613136303934643733346566346366653434643039626535646637353230663839353234363039 6364353566643030620a323733616636666131656263373336383233303636376339316634363061 3262 復号された状態 this_is_txt 上記のサンプルとして記載しているファイルを作成した際の過程をご紹介します。 # ファイルをvimで作成 $ vim encrypt_file $ cat encrypt_file this_is_txt # 暗号化するためのパスワードファイルを作成 $ vim .vault_password_file $ cat .vault_password_file test_password # ↑で作成していたtxtファイルを暗号化(--vault-idオプションにパスワードファイルを指定) $ ansible-vault encrypt --vault-id test @.vault_password_file encrypt_file $ cat encrypt_file $ANSIBLE_VAULT ; 1 . 2 ;AES256; test 64623535646465656235363633623431396365666237653663633962616437663239343762356463 3833333531663736656533643734303763393835383663650a386231626230373937666331306536 36613136303934643733346566346366653434643039626535646637353230663839353234363039 6364353566643030620a323733616636666131656263373336383233303636376339316634363061 3262 # 暗号化したファイルを復号して確認 $ ansible-vault view --vault-id test @.vault_password_file encrypt_file this_is_txt # 暗号化したファイルを復号して修正 $ ansible-vault edit --vault-id test @.vault_password_file encrypt_file ⇒viが起動するので修正 # ↑で修正したファイルの実体を確認 $ cat encrypt_file $ANSIBLE_VAULT ; 1 . 2 ;AES256; test 39643730306632346466383636663961306437333833303931343937376531316363656634393863 3864663636356461613434643036356234323034626234640a323635666362373936356465653534 37343132653463363732653635393037363436366663343262656433663232393338613036656233 6262373666656436340a343865393663323066353430663431653731333966643862653736646632 3334 # 暗号化したファイルを再度復号して確認 $ ansible-vault view --vault-id test @.vault_password_file encrypt_file this_is_txt2 ※各コマンドオプションなどの詳細については公式ドキュメントや私自身が参考にさせていただいた記事のご紹介だけにとどめておきます。 公式ドキュメント Ansible Vaultの使い方の調査 上記のような encrypt_file の作り方はいくつかやり方があるので、 こちら を参考に環境などに合わせて、適切な方法を選択していただければ。 この形式には、 暗号化した後に中身の確認や修正が簡単にできる って特徴があります(後述する 1つの変数のみ 形式との比較になりますが)。 コレが特徴?って思う方もいるかもしれませんが、 次にご紹介する 1つの変数のみ のご紹介を見てもらえると理解してもらえるかなぁ~と思います。 2. 1つの変数のみ 1つの変数のみを暗号化する形式です。(急募:ネーミングセンス) 変数名は暗号化せず、変数の中身だけ暗号化された状態を ansible-playbook コマンドなどで扱えます。 暗号化されてる状態 password_vars: !vault | $ANSIBLE_VAULT;1.2;AES256;test 36656531623938393464666363353630333865316435346533303035663965323862383666356437 3938613939663031343436663634623833396630373965370a306532643662353931313839393735 66623034373862626262333735363461386339373666663935383431383266656463323333393433 3331363264383963300a303039343335666134313862616633663634313035616165376437386436 32343966366636643061643439383235356236303533616566316432616566336330 復号された状態 password_vars: this_is_password 上記のサンプルとして記載している変数を作成した際の過程をご紹介します。 # 暗号化するためのパスワードファイルを作成 $ vim .vault_password_file $ cat .vault_password_file test_password # ↑で作成していたtxtファイルを暗号化(--vault-idオプションにパスワードファイルを指定) $ ansible-vault encrypt_string --vault-id test @~/.vault_password_file --stdin-name password_vars Reading plaintext input from stdin. ( ctrl-d to end input ) this_is_password ←ココでCtrl+D入力 ※ENTERを入力してしまうと末尾に改行が含まれてしまうので要注意 password_vars: !vault | $ANSIBLE_VAULT ; 1 . 2 ;AES256; test 36656531623938393464666363353630333865316435346533303035663965323862383666356437 3938613939663031343436663634623833396630373965370a306532643662353931313839393735 66623034373862626262333735363461386339373666663935383431383266656463323333393433 3331363264383963300a303039343335666134313862616633663634313035616165376437386436 32343966366636643061643439383235356236303533616566316432616566336330 # ↓のようにコマンドラインで暗号化したい文字列を指定するやり方もありますが、 # historyに残ってしまうのであまり好ましくありません(知られたくない文字列のハズなので) # ansible-vault encrypt_string this_is_password --vault-id test@~/.vault_password_file --name password_vars # ↑で生成した文字列をtxtに追記 $ vim encrypt_vars $ cat encrypt_vars password_vars: !vault | $ANSIBLE_VAULT ; 1 . 2 ;AES256; test 36656531623938393464666363353630333865316435346533303035663965323862383666356437 3938613939663031343436663634623833396630373965370a306532643662353931313839393735 66623034373862626262333735363461386339373666663935383431383266656463323333393433 3331363264383963300a303039343335666134313862616633663634313035616165376437386436 32343966366636643061643439383235356236303533616566316432616566336330 この方法には以下の注意点があります。 復号化した状態を修正/確認するのが手間(1つのファイル丸ごととは異なり) 変数名:(平文)+vaultで暗号化した文字列 という構造になってる関係で ansible-vault view や ansible-vault edit などの CLI 上で復号ができません 。 ansible-playbook コマンドなどでdebugモードで実行すれば中身の確認ができます。 (今回利用しているv2.7での挙動ですが、今後のバージョンアップで機能追加されそうな部分な気がします) memo)Ansible Vaultの歴史というか流れのようなもの Ansible Vaultリリース当初(v1.5)はファイル丸ごとの暗号化しかできなかった →その後、変数部分のみ暗号化が機能追加されてリリース(2.2か2.3~) v1.5当時?に書かれたもの: https://qiita.com/yteraoka/items/d18e3c353b6e15ca84a8 機能追加時の参考: https://qiita.com/yunano/items/86d3f9beb678adbff50d v1.5のタイミングはやはりファイル丸ごと暗号化するってのは不便な時があったようで、、、 当時は変数ファイルを分割する などで対処していたようです。 ※この対処法自体は今でも有効に活用できるので、頭の片隅にでも置いておくといいかもしれません。 まとめ 暗号化の単位(というか範囲)は2パターンあるよ! 1つのファイル丸ごと 1つの変数のみ それぞれメリットとデメリットがあるよ! 1つのファイル丸ごと メリット:暗号化したファイルの確認・修正が簡単にできる デメリット:変数名も一緒に暗号化されてるため変数の追跡がしづらい 1つの変数のみ メリット:変数の追跡が ラク にできる デメリット:暗号化した変数の確認・修正が手間 結論)暗号化する対象を見極めて、 1つのファイル丸ごと と 1つの変数のみ の形式をうまく使い分けようね! 具体例) 秘密鍵 ファイルを暗号化したい! ⇒ファイル丸ごとで暗号化! 環境変数 とか対象ホスト毎に変えているvarsも暗号化したい! ⇒変数のみ暗号化して既存varsファイルに追記! 今はべた書きになってるけど、ココの文字列も暗号化しときたいな~ ⇒とりあえず変数とtemplateに分けておく!暗号化するか否かはチーム内で議論して決めよう 何かめっちゃ暗号化してる変数増えてきてんな... 暗号化してる変数だけ別のvarsファイルに出してファイル丸ごと暗号化しちゃえ ⇒変数を追跡するのが難しくなり、チーム内の合意がないと混乱を招きます!(実体験) 変数ファイルの丸ごと暗号化は 慎重に やりましょう おわりに 今回Ansible Vaultについてご紹介しました。 この記事がどなたかの助けになれば幸いです。 ちょっとした小技集(蛇足あるいは自分用のメモ) ラベル差し替え ラベルってなんだろう?って方は こちら 。 ラベルって機能を利用してpasswordと暗号化する部分を紐づけていますが、間違えてラベルを付加してしまうことがあります。。。 その場合、ラベル部分のみは平文で記載& ansible-vault コマンド内部で処理されているようで、 vim などで直接編集することが可能です。 (v2.7ではラベルって機能自体が追加オプションを設定しないとあまり意味がない状態ではありますが...) $ cat encrypt_file $ANSIBLE_VAULT ; 1 . 2 ;AES256;hoge ←ココの末尾がtestになっていてほしかった 64623535646465656235363633623431396365666237653663633962616437663239343762356463 3833333531663736656533643734303763393835383663650a386231626230373937666331306536 36613136303934643733346566346366653434643039626535646637353230663839353234363039 6364353566643030620a323733616636666131656263373336383233303636376339316634363061 3262 $ ANSIBLE_VAULT_ID_MATCH =true ansible-vault view --vault-id test @.vault_password_file encrypt_file ERROR! Decryption failed ( no vault secrets were found that could decrypt ) on encrypt_file for encrypt_file ANSIBLE_VAULT_ID_MATCH=true を追加して ansible-vault view などを実行するとエラーになっちゃいますが、、、 vim で直接修正しちゃえば、、、 $ vim encrypt_file $ANSIBLE_VAULT ; 1 . 2 ;AES256; test ←ココをtestに修正 64623535646465656235363633623431396365666237653663633962616437663239343762356463 3833333531663736656533643734303763393835383663650a386231626230373937666331306536 36613136303934643733346566346366653434643039626535646637353230663839353234363039 6364353566643030620a323733616636666131656263373336383233303636376339316634363061 3262 $ ANSIBLE_VAULT_ID_MATCH =true ansible-vault view --vault-id test @.vault_password_file encrypt_file this_is_txt 同一のコマンドで確認できるようになりました! ( ANSIBLE_VAULT_ID_MATCH=true を外せばイイじゃないか、というのは置いておいて...) ・変数のみの暗号化している部分を修正する 変数名:(平文)+vaultで暗号化した文字列 という構造が ansible-vault view や ansible-vault edit で扱えない要因のため、 イイ感じに不要な部分を除去してやると、 ansible-vault view や ansible-vault edit で扱えるようになります。 $ cat encrypt_vars password_vars: !vault | $ANSIBLE_VAULT ; 1 . 2 ;AES256; test 36656531623938393464666363353630333865316435346533303035663965323862383666356437 3938613939663031343436663634623833396630373965370a306532643662353931313839393735 66623034373862626262333735363461386339373666663935383431383266656463323333393433 3331363264383963300a303039343335666134313862616633663634313035616165376437386436 32343966366636643061643439383235356236303533616566316432616566336330 $ vim encrypt_vars 1 ) 変数名の行を削除 2 ) インテンドを削除 ↓ $ANSIBLE_VAULT ; 1 . 2 ;AES256; test 36656531623938393464666363353630333865316435346533303035663965323862383666356437 3938613939663031343436663634623833396630373965370a306532643662353931313839393735 66623034373862626262333735363461386339373666663935383431383266656463323333393433 3331363264383963300a303039343335666134313862616633663634313035616165376437386436 32343966366636643061643439383235356236303533616566316432616566336330 $ ansible-vault view --vault-id test @.vault_password_file encrypt_vars this_is_password ⇒変数 password_vars の中身が確認できます!(editもできるよ!) ※↑の状態だと変数 password_vars と認識できないので、 ansible-playbook コマンドなどを実行する前にインテンドや変数名の行を再度追加する必要があるので注意 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
普段は楽楽精算開発に関わっているrsrksです。 前回投稿の JUnit ネタ tech-blog.rakus.co.jp に続き 今回はテストを簡易的な仕様書にする方法として JUnit の階層化を紹介します。 階層化 開発の中で 「このメソッドの仕様ってどうなってたっけ?」 と思ったら、まず実装を確認することが多いのではないかと思います。 しかし、実装から仕様を読み取るのはなかなか大変です。 「どのパターンでどのような挙動をするか」 を頭の中だけで網羅的に把握するのは困難です。把握するためにはパターンを表にまとめて仕様を整理する必要がありますね。 このパターンのまとめを JUnit では階層化によって実現できます。 最終的にはテストが簡易的な仕様書になるところまで持っていくことができます。 JUnit での階層化は @Nested アノテーション を付与するだけで実現できます。 ここでは例として「リク エス トで飛んできた名前をバリデートするメソッド」のテストを書きたいとします。 バリデートの仕様は簡単に以下のようにします。 エラーの場合 0文字の場合は必須エラーを返す 10文字以上の場合は文字数オーバーエラーを返す エラーにならない場合はnullを返す 実装例 (メソッドの中身は適当です) // 名前のバリデートを行うメソッドをテスト public class ValidateNameTest { String validateName(String name) { if (name.equals( "" )) { return "必須エラー" ; } if (name.length() >= 10 ) { return "文字数オーバーエラー" ; } return null ; } // 名前が無効な場合 @Nested class NameIsInValid { // 名前が0文字の場合は必須エラーを返す @Test public void nameLengthIs0_returnRequiredError() { assertEquals( "必須エラー" , validateName( "" )); } // 名前が10文字の場合は文字数オーバーエラーを返す @Test public void nameLengthIs10_returnRangeError() { assertEquals( "文字数オーバーエラー" , validateName( "1234567890" )); } } // 名前が有効な場合 @Nested class NameIsValid { // 名前が1文字の場合はnullを返す @Test public void nameLengthIs1_returnNull() { assertEquals( null , validateName( "1" )); } // 名前が9文字の場合はnullを返す @Test public void nameLengthIs9_returnNull() { assertEquals( null , validateName( "123456789" )); } } } 今回は階層が一つだけですが、 @Nested が付いたクラスの下にさらに @Nested をつければ階層はどんどん深くできます。 これだけでも見やすいですが、テストコードが長くなってくると全てのパターンを見るのは中々一苦労です。 そんな時はテストの実行レポートを出力するのがお勧めです。 IntelliJ だとテストを実行した時点で以下のような感じで表示され、一目で挙動を確認できます。 テストへの名前付け 上記でテストを実装しましたが、テストメソッドは全て英語で 命名 しました。 しかし、英語だと 命名 が難しい、、もっと分かりやすいテスト名にしたい、、という悩みが発生します。 そんな時は @DisplayName() という アノテーション があります。 @DisplayName ( "名前のバリデートを行うメソッドをテスト" ) public class ValidateNameTest { String validateName(String name) { if (name.equals( "" )) { return "必須エラー" ; } if (name.length() >= 10 ) { return "文字数オーバーエラー" ; } return null ; } @DisplayName ( "名前が無効な場合" ) @Nested class NameIsInValid { @DisplayName ( "名前が0文字の場合は必須エラーを返す" ) @Test public void nameLengthIs0_returnRequiredError() { assertEquals( "必須エラー" , validateName( "" )); } @DisplayName ( "名前が10文字の場合は文字数オーバーエラーを返す" ) @Test public void nameLengthIs10_returnRangeError() { assertEquals( "文字数オーバーエラー" , validateName( "1234567890" )); } } @DisplayName ( "名前が有効な場合" ) @Nested class NameIsValid { @DisplayName ( "名前が1文字の場合はnullを返す" ) @Test public void nameLengthIs1_returnNull() { assertEquals( null , validateName( "1" )); } @DisplayName ( "名前が9文字の場合はnullを返す" ) @Test public void nameLengthIs9_returnNull() { assertEquals( null , validateName( "123456789" )); } } } ↓実行結果も日本語で表示されます。 まとめ 今回はテストを簡易的な仕様書にする方法として JUnit の階層化を紹介しました。 テストの階層化自体は知っていたけど、何のために行うのかを意識してなかった方はぜひ「テストの仕様書化」を意識してもらえたらと思います。 参考 JUnit 5 User Guide エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは、あるいはこんばんは。すぱ..すぱらしいサーバサイドのエンジニアの( @taclose )です☆ 今回は 『 zsh を使って更に普段の業務の作業効率をあげよう!』 というテーマでお話をしたいと思います! これまで日常業務の作業効率やミスを減らすためにいくつかの記事を書いてきました( ssh ばかり)。 もうここまできたらさすがに ssh ネタはないでしょ?と思ってるそこのあなたっ これは ssh だけに限った話ではないですが、実は zsh を使うとコマンドの入力補完等により作業効率3倍、入力ミス90%減、身長3mmアップします!(筆者の体感) 具体的には ssh のログインやgitコマンドで TABキー を押すと補完がきいたり、ブランチ名が表示されたりコマンド履歴が便利に使えたり..etc この記事が読み終わった頃には以下のようなことができます! zsh に切り替えたコンソール zshとは? zshをインストールしよう! zshインストールされてないよね?確認! 最新バージョンを確認しよう インストール前の準備 zshのソースインストール zshを使いやすくカスタマイズしよう!(oh-my-zsh) oh-my-zshとは? oh-my-zshのインストール oh-my-zshに自分独自の設定を入れましょう! もしBashに戻したいのなら... 最後に 参考文献 zsh とは? 難しい話は置いといて、概要だけいうと、 zsh (ズィーシェル、もしくはそのまま zsh と呼ばれている)とは、簡単に言うと bash の上位互換 です。 ほかにもsh, ksh , bash , csh , tcsh とかありますが、普通にパソコン触ってれば shが初期、 bash が標準、 zsh は上位互換(機能拡張版) csh , tcsh は BSD UNIX とかで使われててるシェルだから関わる事はあまりない とだけわかっていれば十分です。ちなみに現在では MacOS では標準シェルとして zsh が採用されてます 。今回はそんな zsh を CentOS とかでも使いましょうという企画なわけですね! zsh をインストールしよう! zsh インストールされてないよね?確認! # 今自分が使っているシェルがzshではなくbashである事を確認 $ echo $SHELL /bin/bash # zshがインストールされていない事を確認 $ chsh -l /bin/sh /bin/bash /usr/bin/sh /usr/bin/bash はい、chshで zsh が一覧にないのでインストール必要そうですね!次! 最新バージョンを確認しよう 早速インストールなんですが、現在(2023年1月4日) zsh の最新は5.9となっています。 ZSH - Source download yum でインストールするとしたらversionいくつかな? $ yum info zsh で確認 ちょっと古いですね。今回はソースダウンロードして最新を入れましょう。 インストール前の準備 ncurses-develをインストールしましょう!これを入れないと zsh のインストール時に以下のようなエラー出ちゃいますよ! configure: error: in `/usr/local/src/zsh-5.9': configure: error: "No terminal handling library was found on your system. This is probably a library called 'curses' or 'ncurses'. You may need to install a package called 'curses-devel' or 'ncurses-devel' on your system." See `config.log' for more details ちなみに ncursesというのはTUI(TextUI)つまり Windows でいう ブルースクリーン 的なインタフェースを提供するための API 群の集まりです。 zsh はこれを使う事で入力補完とかを円滑に行えるようにしようとしてるわけですね!エレガント! 以下がncurses-develのインストール手順です。 # zshに必要なncurses-develをインストール $ yum install -y ncurses-devel zsh のソースインストール さ、お楽しみの zsh 早速入れていきましょう! # sourceダウンロードする前にディレクトリを移動 $ cd /usr/ local /src/ # ダウンロード $ wget https://sourceforge.net/projects/zsh/files/zsh/ 5.9 /zsh -5.9 .tar.xz/download -O zsh -5.9 .tar.xz # 解凍 $ tar xvf zsh -5.9 .tar.xz # 解凍したディレクトリに移動 $ cd zsh -5.9 / # マルチバイトを有効にしてビルドしてインストール $ ./configure --enable-multibyte $ make && make install # zshを使える状態にする $ echo /usr/ local /bin/zsh >> /etc/shells # ログイン時のシェルをzshに変更する $ chsh -s /usr/ local /bin/zsh # 一旦シェルを終了する $ exit これで zsh のインストールは終わり!ソースインストールとか慣れてない方にとっては英語の羅列が気持ち悪いだけですが、インストールがErrorやFatal!warning!!みたいになってないかぐらいは確認しておけばだいたい問題ないですよっ Self-signed certificate encountered. To connect to raw.githubusercontent.com insecurely, use `--no-check-certificate'. もし wget で上記のようなエラーが出た人は wget コマンド実行時に --no-check-certificate オプションを付与してくださいね 。 zsh を使いやすくカスタマイズしよう!(oh-my- zsh ) 早速シェルを立ち上げてみたが・・・これだけでは改善効果が感じられませんね。 せいぜい cd を入力途中で[Tabキー]連打で候補が次から次へと変わるぐらいです。かといって zsh の設定は多岐に渡るため 今回はoh-my- zsh というオススメ設定を紹介します。 oh-my- zsh とは? zsh の設定は .zshrc に記載するのですが、良い感じにその初期設定ファイルを生成したり、テーマ変更という形でプロンプトのデザインをさらっと変えられるようになります。 oh-my- zsh のインストール # oh-my-zshをインストール sh -c " $( wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh ) " これだけ!もしgit cloneでこけたよ!って人とかいたら、原因様々ですが普段git問題なく使えてる方ならば install.shが内部的に https の リポジトリ 指定してるのが癖ある ので、それかもしれません。 Cloning Oh My Zsh ... fatal: unable to access ' https://github.com/ohmyzsh/ohmyzsh.git/': Peer's certificate issuer has been marked as not trusted by the user. こんなエラーが出た方は一旦以下のコマンドを実行してしまえば問題なく次はインストール出来るかと思います。 $ git config --global http.sslVerify false oh-my- zsh に自分独自の設定を入れましょう! まずはこれまで .bashrc に記載した独自の設定を .zshrc の一番下に転記しましょう 次に .zshrc の11行目辺りに書かれている ZSH_THEME="xxxxx" という所に好きなテーマを指定しましょう。とりあえず eastwood にしてみてください。 $ source ~/.zshrc と入力して zsh の設定を反映させるとデザインが変わるはずです 他にもテーマはたくさんあります!以下から選んで下さいね! (oh-my- zsh Wiki : Themes) https://github.com/ohmyzsh/ohmyzsh/wiki/Themes もし Bash に戻したいのなら... 万が一、あまり zsh がお気に召さないという方は以下の手順で元通りです! # 使えるシェルを確認。 $ chsh -l /bin/sh /bin/bash /usr/bin/sh /usr/bin/bash # ログインシェルをBashに戻す $ chsh -s /usr/bin/bash # 不要なファイルを削除 $ rm -rf ~/.oh-my-zsh $ rm -rf ~/.zshrc 最後に お疲れ様でした!ここまでやると以下のような入力補完がバシバシ効いたUIになったんじゃないでしょうか。 今回はgitコマンドや ssh の入力補完を例にしていますが、 プラグイン 次第でもっともっと使いやすくできます。 うんうん、これで ssh コマンドの入力ミスも起きないですね! 尚、 ssh の後にタブキーで入力候補で __taclose.develop 等が出てくるのは、 以前sshの設定に関するまとめ記事 を読んでくださいね! . ssh /configの設定をすればOKです! また機会があれば続編やろうかな!お疲れ様でした!! 参考文献 tech-blog.rakus.co.jp 最新のzshをインストールする手順 - Qiita ohmyzsh - GitHub オレオレ証明書なGitへの新規リポジトリ作成 - Qiita エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは、uemura_rks です。 個人的な勉強目的で GitLab 上で AWS Lambda のデプロイを自動化 してみました。 GitLab での CICD や AWS SAM、あとは Docker に興味を持っている方に向けて、その構築履歴を紹介したいと思います。 基本的には各ツールのドキュメントを参照しつつ、追記した設定などを共有していきます。 作りたいもの 1.GitLab 構築 2.GitLab Runner 構築 GitLab Runner の登録 登録トークン取得 登録コマンドの実行 3.Lambda 環境構築 AWS SAM チュートリアル 4.デプロイ自動化 5.自動デプロイの確認 Lambda の変更 テンプレートの変更 終わりに 参考 作りたいもの ローカルに Docker で GitLab や GitLab Runner を立てつつ、プッシュしたら AWS 上に自動デプロイが走る構成を目指します。 GitLab に push したら GitLab Runner が走る。 GitLab Runner から ジョブ実行用コンテナを起動。 コンテナには AWS SAM CLI が使えるイメージを利用します。 AWS SAM CLI を使って Lambda 本体や AWS リソースを定義した Stack をデプロイ。 1.GitLab 構築 参考: GitLab Docker images | GitLab さっそく GitLab の構築からはじめていきます。 Docker 上に構築するので、参考ドキュメントの docker-compose.yml の部分をベースとして拝借していきます。 version : '3' services : gitlab : container_name : gitlab image : 'gitlab/gitlab-ce:latest' restart : always hostname : 'local-gitlab' environment : GITLAB_OMNIBUS_CONFIG : | external_url 'http://local-gitlab:8929' ports : - '80:8929' volumes : - ./gitlab/config:/etc/gitlab - ./gitlab/logs:/var/log/gitlab - ./gitlab/data:/var/opt/gitlab shm_size : '2gb' ドキュメントでは エンタープライズ 版の gitlab-ee イメージを指定していますが、 今回は個人利用なのでコミュニティ版の gitlab-ce を使います。 volumes: セクションに関して、フォルダ構成はこんな感じにしています $ tree ├── gitlab (GitLab の volume) ├── gitlab-runner (GitLab Runner の volume とする予定) ├── docker-compose.yml └── sampleapp (サンプルアプリを入れる予定) 一度立ち上げてみます docker-compose up -d 名前解決できるようにローカルマシンの hosts ファイルに 127.0.0.1 local-gitlab を追記すると GitLab( http://local-gitlab/ ) でアクセス出来るようになります。 初回は root ユーザでログインすることになりますが、 root のパスワードはこちらのコマンドで知ることが出来ます。 # Visit the GitLab URL, and sign in with the username root and the password from the following command: sudo docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password 2.GitLab Runner 構築 参考: Run GitLab Runner in a container こちらもドキュメントに載っている docker run コマンドを参考に、 docker-compose.yml に起こしていきます。 version : '3' networks : gitlab-network : name : gitlab-network driver : bridge services : gitlab : container_name : gitlab image : 'gitlab/gitlab-ce:latest' restart : always hostname : 'local-gitlab' environment : GITLAB_OMNIBUS_CONFIG : | external_url 'http://local-gitlab:8929' ports : - '80:8929' volumes : - ./gitlab/config:/etc/gitlab - ./gitlab/logs:/var/log/gitlab - ./gitlab/data:/var/opt/gitlab shm_size : '2gb' networks : - gitlab-network runner : container_name : gitlab-runner image : gitlab/gitlab-runner:latest restart : always volumes : - ./gitlab-runner/config:/etc/gitlab-runner - /var/run/docker.sock:/var/run/docker.sock networks : - gitlab-network ついでに内部ネットワークを作成して、二つのコンテナを同じネットワークに入れておきます。 GitLab Runner の登録 GitLab Runner コンテナも立ち上げたら、GitLab に Runner を登録していきます。 登録 トーク ン取得 参考: Registering runners (deprecated) | GitLab Runner の登録に入る前に GitLab の登録 トーク ンというものを取得しておきます。 GitLab に リポジトリ を作ると、 リポジトリ の Settings > CI/CD > Runners から GitLab Runner の登録時に必要な「GitLab の URL」と「登録 トーク ン」が取得できます。 注意点として、今紹介している登録 トーク ンを使った方法は、現在の GitLabバージョン15.6 では deprecated となっております。 新しく認証 トーク ンによる登録ができるようになる予定みたいですが、現在はまだ登録 トーク ンを使うしかないのでそのまま進めます。(絶妙にタイミングが悪かったです。) 登録コマンドの実行 参考: one-line-registration-command Runner の登録に必要な URL と登録 トーク ンが確認できたので、GitLab Runner コンテナに入って register コマンドを実行していきます。 gitlab-runner register \ --non-interactive \ --url "http://local-gitlab:8929/" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \ --executor "docker" \ --docker-image alpine:latest \ --description "docker-runner" \ --docker-network-mode gitlab-network --url :docker ネットワーク内の通信になるので、コンテナポートを付け足しておきます。 --docker-image :後に作成する .gitlab-ci.yml にコンテナイメージを指定しなかった場合のデフォルトになるみたいです。今回は使う予定がないのでサンプルのまま登録しています。 --docker-network-mode :docker-compose.yml で用意したネットワークを指定しておきます。 登録に成功すると GitLab に「Available specific runners」が増えています。 3.Lambda 環境構築 GitLab Runner の構築までできたので、次は Lambda を用意していきます。 「Lambda のデプロイを自動化する」というゴールから考えると Lambda 単体で試してもよかったのですが、 前から興味があったという理由で今回は AWS SAM を使ってみます。 この投稿では触れませんが、 AWS SAM はローカル環境も手に入るのが良いです。 AWS SAM チュートリアル 参考: チュートリアル: Hello World アプリケーションのデプロイ - AWS Serverless Application Model チュートリアル の Hello World アプリケーションをデプロイしてみます。 API Gateway と Lambda ですね。 チュートリアル の #Step 1 をローカルで実行します。 #Step 1 - Download a sample application sam init すると、「 hello world 」と出力するだけの Lambda 関数とインフラ定義ファイルの template.yml が作成されます。 一度この状態でローカルからデプロイしてみます。 #Step 2 - Build your application cd sam-app sam build   #Step 3 - Deploy your application sam deploy --guided dry-run の結果を教えてくれます。 Waiting for changeset to be created.. CloudFormation stack changeset ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Operation LogicalResourceId ResourceType Replacement ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Add HelloWorldFunctionHelloWorldPermissionProd AWS::Lambda::Permission N/A + Add HelloWorldFunctionRole AWS::IAM::Role N/A + Add HelloWorldFunction AWS::Lambda::Function N/A + Add ServerlessRestApiDeploymentxxxxxxxxxx AWS::ApiGateway::Deployment N/A + Add ServerlessRestApiProdStage AWS::ApiGateway::Stage N/A + Add ServerlessRestApi AWS::ApiGateway::RestApi N/A ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:000000000000:changeSet/samcli-deploy0000000000/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Previewing CloudFormation changeset before deployment ====================================================== Deploy this changeset? [y/N]: AWS SAM は CloudFormation を拡張したサービスらしいので、メッセージに CloudFormation という単語がちょくちょく出てきますね。 問題ないので「Deploy this changeset? 」に y で答えてデプロイを実行します。 無事に CloudFormation に Stack ができています。 Lambda や API Gateway を個別に見に行ってもちゃんとできています。 デプロイできたので、 curl を叩いて hello world が返ってくることを確認しておきます。 $ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello {"message": "hello world"} 4.デプロイ自動化 参考: GitLab CI/CD を使用したデプロイ - AWS Serverless Application Model AWS SAM だけでも1コマンドでデプロイできるようになるので十分便利なのですが、極力人の操作を減らしたいです。 GitLab Runner を使って、プッシュしたら自動で SAM アプリがデプロイされるようにしていきます。 #Step 2 - Build your application cd sam-app sam build   #Step 3 - Deploy your application sam deploy --guided 先ほど手で実行した↑の部分が CI/CD 部分ということで、ここを .gitlab-ci.yml に切り出します。 image : public.ecr.aws/sam/build-python3.9 stages : - build - deploy job_build : stage : build script : - cd sam-app - sam build job_deploy : stage : deploy variables : GIT_CLEAN_FLAGS : none script : - cd sam-app - sam deploy --no-confirm-changeset --no-fail-on-empty-changeset GitLab Runner のジョブ実行用コンテナには AWS が提供しているイメージを利用します。 AWS SAM CLI が最初から入っているので助かります。 また、ジョブ実行時に利用される IAM のアクセスキーなどを GitLab の Variables に登録しておきます。 参考: Deploying AWS Lambda function using GitLab CI/CD | GitLab リポジトリ の Settings > CI/CD > Variables から登録できます。 あとは、 .gitlab-ci.yml をプッシュすれば GitLab Runner ジョブが立ちあがるようになります。 5.自動デプロイの確認 AWS SAM の Lambda と テンプレートをいじって push→deploy してみます。 Lambda の変更 Hello World アプリケーションで元々 コメントアウト されていた グローバルIP 取得の処理を アンコメントしてみます。 import json import requests def lambda_handler (event, context): ~~~ ~~~ try : ip = requests.get( "http://checkip.amazonaws.com/" ) except requests.RequestException as e: # Send some context about this error to Lambda Logs print (e) raise e return { "statusCode" : 200 , "body" : json.dumps({ "message" : "hello world" , "location" : ip.text.replace( " \n " , "" ) // ★ IP出力部分をアンコメント }), } テンプレートの変更 参考: FunctionUrlConfig - AWS Serverless Application Model API Gateway を破棄して、代わりに Lambda の 関数 URL をエンドポイントとして用意してみます。 Resources : HelloWorldFunction : Type : AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties : CodeUri : hello_world/ Handler : app.lambda_handler Runtime : python3.9 Architectures : - x86_64 FunctionUrlConfig : AuthType : NONE Outputs : HelloWorldFunctionUrlEndpoint : Description : "Hello World Lambda Function URL Endpoint" Value : !GetAtt HelloWorldFunctionUrl.FunctionUrl Properties:Events: セクションで定義されていた API Gateway を 関数 URL の設定に置き換えています。 変更が終わったので、プッシュして、GitLab の Pipeline が走ることを確認します。 Pipeline job の実行ログ 無事に Pipeline が走って、 Lambda Function → Modify Function URL → Add API Gateway → Delete されています。 最後に確認として変更後のエンドポイントに curl してみます。 $ curl https://xxxxxxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws/ {"message": "hello world", "location": "3.115.88.55"} IPアドレス も返ってきました。 終わりに ひとまず GitLab + GitLab Runner + AWS SAM という構成で、プッシュしたものを AWS 上に自動でデプロイする土台は作ることができました。 昔、Lambda をマネジメントコンソールから毎回アップロードしていたことがあったので、今後はその手間から解放されそうです。 また、私は Docker についても最近触り始めたばかりなのですが、「GitLab + GitLab Runner」という構成は複数コンテナを組み合わせた構成ということもあって、Docker 初学者にとって良い練習教材になりました。 実業務では自動デプロイだけでなく UT や SAST など自動テストの考慮も必要になると思います。 それらと今回作ってみた構成の相性については、別途検証が必要かなと考えています。 参考 公式ドキュメント以外で参考にさせていただいたブログです。 Dockerコンテナでgitlabとgitlab-runnerを構築してCI/CD | SyachikuLOG エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは、sakekobaと申します。 今回は人気の高いGo言語の「メソッド」について記事を書きたいと思っております。 また、Go言語については、当ブログで先輩方が素敵な記事を複数記載されております。 こちらも併せてご覧頂けましたら幸いです。 Go言語 入門【環境構築とコーディング】 - RAKUS Developers Blog | ラクス エンジニアブログ Go言語の平行処理をやってみよう!【goroutine】 - RAKUS Developers Blog | ラクス エンジニアブログ 目次 目次 メソッドとは? Go言語のメソッドは何に紐づくの? メソッドの書き方、使い方 同一のメソッド名について インターフェースについて 最後に メソッドとは? Go言語だけでなく、メソッドを持つ言語は多いと思います。 「メソッド」と聞くと、まず オブジェクト指向 プログラミングを思い浮かべる方も多いのではないでしょうか。 メソッドとは、主に オブジェクト指向言語 で使われる言葉で、オブジェクトが持つ処理や手続きの方法を指す言葉です。 例えば、 「車」というオブジェクトであれば「走る」「ドアを開ける」「ドアを閉じる」等の処理(メソッド)が紐づいているイメージです。 Java 等では「クラス」という形でオブジェクトを定義して、そのクラスにメソッドも定義されていて、そこから インスタンス を生成して使う。。のような手順を踏みます。 ところが、Go言語はこの上記のような「クラス」は使用されません。 では、Go言語のメソッドはどこに定義されるのでしょうか。 Go言語のメソッドは何に紐づくの? Go言語の場合は「型」に直接紐づきます。 「型」といっても、Go言語の型はintやstringといった基本の型だけでなく、type という型を定義する 予約語 を用いて基本の型を拡張し、オリジナルの型を作ることが出来ます。 この type を使って拡張した型に、メソッドを追加することが出来ます。 たとえば、基本の型であるstring型を拡張(string型に様々な機能を追加出来るように)するMyStringという型を作る場合、Go言語では下記のように書いて定義することが出来ます。 type MyString string この新しく定義したMyStringに、下記のようにメソッドを追加することが出来ます。(例は敬称の「さん」を返すメソッド) func (s MyString) Keisyo() string { return "さん" } このように型に紐づけるのがGo言語におけるメソッドです。 メソッドの書き方、使い方 Go言語のメソッドは func (レシーバ 型) メソッド名(引数) 戻り値 { 処理 } という形で書きます。 (レシーバ 型)の部分は、メソッドが紐づく型(typeで指定した拡張型)と、その型からメソッドを呼び出す際に指定した変数が持つ値を受け取る変数名です。 先ほどの例では、 レシーバとして変数sを用意し、レシーバの型がtypeを使って定義したMyString、メソッド名がKeisyo()で、戻り値がstringです。 func (s MyString) Keisyo() string { return "さん" } 呼び出す際は下記のように、レシーバの型で変数を作成し、その変数にドットをつけてメソッド名を記載します。 func main() { namae := MyString( "たなか" ) fmt.Print(namae, namae.Keisyo()) } こうして呼び出したメソッドは、呼び出し元(ドットの前の型)と一致するレシーバの型を持つメソッドが呼び出されます。 型の定義、メソッドの定義から呼び出しまでを続けて書くと下記のような形になります。 package main import ( "fmt" ) type MyString string func (s MyString) Keisyo() string { return "さん" } func main() { namae := MyString( "たなか" ) fmt.Print(namae, namae.Keisyo()) } ⇒出力結果 たなかさん になります。 同一のメソッド名について メソッドは、「呼び出しに指定した型と一致するレシーバを持つメソッド」が呼び出されます。 つまり、レシーバの型さえ違えば、同一のメソッド名を作成することが可能です。 例えば、先ほどの例では Keisyo() を呼び出すと「さん」が返ってきていました。 ただ、呼び出し元の型によって敬称を使い分けたい場合、下記のようにtypeで型を複数定義し、typeで拡張した型ごとにメソッドを持たせることで、同じメソッド名で違う振る舞いをさせることが出来ます。 package main import ( "fmt" ) type MyString1 string type MyString2 string func (s MyString1) Keisyo() string { return "さん" } func (s MyString2) Keisyo() string { return "くん" } func main() { namae1 := MyString1( "たなか" ) namae2 := MyString2( "さとう" ) fmt.Println(namae1, namae1.Keisyo()) fmt.Println(namae2, namae2.Keisyo()) } ⇒出力結果 たなか さん さとう くん になります。 インターフェースについて 上記のように、同一のメソッド名を複数の型に持たせることが出来ます。 ただ、呼び出し元と同一のメソッド名しか呼び出せません。 Go言語は静的型付け言語です。変数がカッチリ定義されているので、1つの型として定義された変数には、たとえstringを基にしただけの似たような型であっても、別の型の変数を入れることは出来ません。 func main() { onamae := MyString1( "たなか" ) // ここでonamaeはMyString1の型として定義されてしまいます fmt.Println(onamae, onamae.Keisyo()) onamae = MyString2( "さとう" ) // onamaeはMyString1の型として使われたためエラーになります fmt.Println(onamae, onamae.Keisyo()) } つまり、Go言語で複数の型を使い分けようとすると、このままではif文等で分岐して型ごとの処理を書く必要があり、 ソースコード としてはとても煩雑になります。 どちらもKeisyo()というメソッド名を持っているので、まとめられたら楽そうです。 そのような時に使えるのが、まとめてメソッドを定義、実行できるインターフェースです。 Go言語のインターフェースの定義はシンプルで、下記のように記載すると、stringの戻り値を持つKeisyo()メソッドを持つ拡張型は全て、ONAMAEインターフェースの仲間入りをすることが出来ます。 type ONAMAE interface { Keisyo() string } こうしておくと、先ほどのMyString1型も、MyString2型も、Go言語ではONAMAEインターフェースで使うことが出来ます。 そのため下記のように、ONAMAEインターフェースに型を入れるだけで同じ呼び出し方法 fmt.Println(onamae,onamae.Keisyo()) で違う振る舞いをさせることが出来ます。 package main import ( "fmt" ) type ONAMAE interface { Keisyo() string } type MyString1 string type MyString2 string func (s MyString1) Keisyo() string { return "さん" } func (s MyString2) Keisyo() string { return "くん" } func main() { var onamae ONAMAE onamae = MyString1( "たなか" ) fmt.Println(onamae, onamae.Keisyo()) onamae = MyString2( "さとう" ) fmt.Println(onamae, onamae.Keisyo()) } ⇒出力結果 たなか さん さとう くん になります。 関数化しておくなどで使いまわす場合、おなじstringを戻り値としたKeisyo()さえ持っていれば、例えば「様」や「どの」を追加したくなった時など、色々な敬称の処理を追加しやすくなります。 また、インターフェースは下記のように、中に入っているタイプによって処理を変える事も出来る為、受け取った内容によって振る舞いを変えたいけど、処理をまとめたいという場合にはとても便利だと感じます。 switch onamae.( type ) { case MyString1: fmt.Println( "こんにちは" ) case MyString2: fmt.Println( "やっほー" ) } 今までのコードと繋げると、下記のようにonamaeに入った型によって、挨拶が変わります package main import ( "fmt" ) type ONAMAE interface { Keisyo() string } type MyString1 string type MyString2 string func (s MyString1) Keisyo() string { return "さん" } func (s MyString2) Keisyo() string { return "くん" } func main() { var onamae ONAMAE onamae = MyString1( "たなか" ) fmt.Println(onamae, onamae.Keisyo()) switch onamae.( type ) { case MyString1: fmt.Println( "こんにちは" ) case MyString2: fmt.Println( "やっほー" ) } onamae = MyString2( "さとう" ) fmt.Println(onamae, onamae.Keisyo()) switch onamae.( type ) { case MyString1: fmt.Println( "こんにちは" ) case MyString2: fmt.Println( "やっほー" ) } } ⇒出力結果 たなか さん こんにちは さとう くん やっほー になります。 最後に 今回はGo言語のメソッドについて、記事を書かせて頂きました。 ただ、私は普段Go言語を使って業務を行っているわけではありません。 むしろインフラ管理等を行っており、私はあまりプログラムに馴染みが無い状態です。 今、 ラク スではSRE課の方が先導して、Go言語の勉強会を開催してくださっています。 私のような初心者でも分かるよう、実例を交えてイメージしやすく教えてもらうことが出来、この記事を書こうと思えるまでに理解を深めることが出来ました。 直近の実務のことだけでなく、将来を見据えてGo言語のような需要がありそうな技術の勉強会を開催頂き、そこへの参加機会があるのは、 ラク スの良いところだと思います! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
今年も早いものでもう年末です。 大掃除を意識した時に、「普段からこまめにやっておけば...」と毎年後悔しています。 そんな私とは違って、 PostgreSQL には普段からデータをこまめに掃除してくれる優秀な「VACUUM」という機能があります。 しかも ゴミがでやすい時は小まめに、そうでないときは手を抜いてゆっくりやる というように調整しながら掃除をしてくれます! ですがしっかりお世話をしないと、「あまりにも時間がかかりすぎる」「途中でやめてしまった」といったトラブルが発生します。 今回はそんな PostgreSQL のVACUUM機能を紹介したいと思います。 PostgreSQLのレコード更新/削除のしくみと問題点 1. PostgreSQLにおけるレコードの更新/削除のしくみ 2. ゴミデータが溜まることの問題点 PostgreSQLにおけるVACUUMの役割 1. 不要領域の回収/削除 2. トランザクション周回問題の防止 3. プランナ用の統計情報の更新 VACUUMの特徴 1. 実行手順 2. VACUUMの種類 AUTO VACUUM VACUUM FULL 実行タイミング AUTO VACUUM 設定ファイルの確認 1. VACUUMが実行されるタイミングを知る 実験 AUTO VACUUMの実行 ロングトランザクション 2. VACUUMのチューニングを実施する チューニング方法 バキュームの実行時間を短くしたい 任意のテーブルにバキュームの設定値をセットする CASE 1: 任意のテーブルのみ頻繁/緩やかにVACUUMが実行されるようにする CASE 2: 任意のテーブルを自動バキュームの対象外にする AUTO VACUUMの課題 VACUUM FULL 使い方 OSに空き領域を返してくれることを確認する VACUUM FULLの課題 終わりに 検証環境 vagrant + virtual box ubuntu20.24 + PostgreSQL 12 vagrant については vagrant+virtual box でDockerを動かす記事 もありますので是非ご一読ください。 PostgreSQL のレコード更新/削除のしくみと問題点 PostgreSQL は、実は裏でゴミデータを削除してくれています。 が、そもそもゴミデータとは何なのでしょうか? まずは、なぜそのゴミデータが出来上がるのか?と、それによる問題点を整理します。 1. PostgreSQL におけるレコードの更新/削除のしくみ PostgreSQL は、 追記型 アーキテクチャ を採用しています。 例えば、あるレコードの更新を行った場合は直接そのレコードを書き換えるのではなく、以下のような動作をします。 更新対象のレコードに削除フラグ(目印)を付ける 更新後のレコードを追加する 追記型の説明 この場合、グレーアウトされたレコードは使用していないにも関わらず残っています。 これが不要領域、いわゆる ゴミデータ です。 削除時も同様で、例えば100万件レコードを削除した場合はそれらのレコードに削除マークを付与するだけなので、100万件分のデータが残ったままになります。 またレコードだけでなく、インデックスも残ってしまいます。 2. ゴミデータが溜まることの問題点 このようなゴミデータが溜まり続けると、以下の問題が発生します。 ゴミデータが溜まり続けることでディスク使用量が増える メモリ上の共有バッファにゴミデータが増え、メモリアクセスではなくディスクアクセスが発生する ⇒ 性能劣化 上記の問題点を解決する方法として「VACUUM」という PostgreSQL の機能を使いましょう! PostgreSQL におけるVACUUMの役割 大きく3つの役割があります。 1. 不要領域の回収/削除 VACUUMは、ゴミデータと呼ばれる不要領域を再利用可能な領域にリサイクルしてくれたり、ディスクの空き容量を増やしてくれたりします。 VACUUM実行後 VACUUMを実行することで、2つのファイルが生成/更新されます。 空き領域マップ(FMS: Free Scan Map) 空き領域が登録されたマップ。 例えばレコードを追加する場合、空いている場所がどこにあるのかを探すときにこのマップがあれば便利です。 可視性マップ(VM: Visibility Map) どこにゴミデータがあるかを記録したマップ。 これにより、ゴミデータがある部分のみVACUUM処理を行えばよいという判断が可能なため、vacuum処理を速く完了させることができます。 可視性マップ その他の恩恵として、インデックスオンリースキャンが実行できるようになります。 2. トランザクション 周回問題の防止 PostgreSQL には、有名な トランザクション 周回問題という問題があります。 見えていてたはずのデータが突然見えなくなってしまうという現象が発生することを指します。 PostgreSQL では トランザクション にIDが割り振られています( トランザクション ID = XID)。 例えばレコードの追加を行った場合、追加したレコードのシステムカラムにXIDが登録されます。 XIDは32bit(約42億個)で管理されています。 XIDを全て使い切った場合は トランザクション を新規で発行できなくなるため、0から再利用する仕組みとなっています。 つまりXIDは循環するようにして割り当てられます。 PostgreSQL では MVCC を使用しデータ管理を行っています。 実行中の トランザクション は、自身が持っている トランザクション よりも古いXIDは見れますが、新しいXIDは見れないしくみになっています。 そこで、新しいXIDを使い続ければ自分より古いXIDが新しいXIDに変わってしまうため見えなくなります。 例えば以下の図のようにXIDが20億進むと、過去のXIDが未来のXIDに変わってしまうため、本来見れるはずのレコードが見れなくなってしまいます。 これが トランザクション 周回問題です。 下図参照 トランザクション 周回問題1 トランザクション 周回問題2 この問題は、FREEZE処理をVACUUMで行うことによって解決されます。 FREEZE処理とは、XIDに凍結されたことを示すマークを付けることです。 マークがあるXIDは全て過去のものとして扱うことができます。 具体的には、xminを「2(特殊なXID)」に書き換える処理を行っています。 また、設定値を変更することでどの程度XIDが消費されたらFREEZE処理を開始するといったコン トロール ができます。 その他にも、XIDが枯渇しないような仕組みがデフォルトで組み込まれています。 本記事では紹介しませんが、代わりに公式マニュアルを紹介させて頂きます。 www.postgresql.jp 3. プランナ用の統計情報の更新 詳細の説明は省きますが、効率良く SQL を実行するための「実行計画」を作成するプランナが利用する統計情報(データの分布などの情報)を更新してくれます。 実行計画についてはコチラの記事をぜひご参考ください。 soachr.hatenablog.com このようにVACUUMはゴミデータを削除してくれたり、XIDを凍結状態にすることで トランザクション 周回問題を防止します。 VACUUMの特徴 1. 実行手順 VACUUMは以下の 7つの手順 で実行されます。 手順 フェーズ 説明 1 initializing ヒープをスキャンし始める準備フェーズ。 2 scanning heap ヒープのスキャンを実行します。テーブルの先頭からゴミデータを探索します。発見したゴミデータのIDをメモリに乗せる。 maintenance_work_mem で設定可能。この値を越えた場合はスキャンを中断し、手順3に進みます。 3 vacuuming indexes インデックスのバキュームを実行します。フルスキャンするため最も時間がかかる処理。Postgres13で実装された パラレルスキャン により高速化を実現しています。 4 vacuuming heap テーブルのバキュームを実行します。 VM を使って必要箇所のみバキューム実行を行います。 heap_blks_scannedがheap_blks_totalより少ない場合、システムはこのフェーズの完了後に手順2のヒープのスキャン処理に戻ります。 5 cleaning up indexes インデックスの整理を行います。後始末フェーズです。 6 truncating heap テーブルの末尾を切り詰めるなどの後始末を行います。 7 performing final cleanup 空き容量マップをバキュームしたり、pg_classの統計処理を更新するなどの後始末を行います。 最も重要なフェーズは3番目のINDEX VACUUMです。 INDEX VACUUMはフルスキャンが発生するため実行時間が他のフェーズよりもかかります。 VACUUMに時間がかかっている場合は、ログを確認してINDEX VACUUMが複数回実行されてるかどうかを確認し、必要であればmaintenance_work_memを増やす対策が必要です。 ログの確認方法については後述の情報をご参照ください。 2. VACUUMの種類 VACUUMは、AUTO VACUUMとVACUUM FULLの2パターンがあります。 それぞれの特徴について軽く触れておきます。 AUTO VACUUM AUTO VACUUMは、設定値に従って自動的にVACUUMを実行してくれます。 基本的にデフォルトでONになっています(古いバージョンではOFFになっています!)。 VACUUM実行中であっても排他ロックを取らないため、レコードの参照等が可能です。 cronのようにVACUUMを定時で実行することができません。 デフォルト設定値もそれなりに適切なため、VACUUMは基本的にAUTO VACUUMを利用しておけば問題ないかと思います。 VACUUM FULL VACUUM FULLは自動ではなく明示的にVACUUMを実行します。 回収した不要領域をOSに戻してくれるためHDDなどの記憶領域が増えます。 その代わり、VACUUM実行中はテーブルに強力な排他ロックをかけるためSELECTすらできなくなるので気を付ける必要があります。 実行タイミング VACUUMが実行されるタイミングは大きく2つあります。 定時および設定値を越えた時 トランザクション 周回問題を防止する時 では、それぞれの特徴と実際の動作を確認してみましょう。 AUTO VACUUM AUTO VACUUMはデフォルトでONになっていますが、明示的に指定する場合は postgresql .confファイルから設定可能です。 設定ファイルの確認 設定ファイルは下記コマンドで探せます。 find / -name postgresql.conf 2> /dev/null 設定を確認してみましょう。 less /etc/postgresql/12/main/postgresql.conf #autovacuum = on ここの コメントアウト を外すと明示的に有効になります。 なお track_counts = on も必要ですが、こちらもデフォルトでONになっています。 #------------------------------------------------------------------------------ # AUTOVACUUM #------------------------------------------------------------------------------ #autovacuum = on # Enable autovacuum subprocess? 'on' # requires track_counts to also be on. #log_autovacuum_min_duration = -1 # -1 disables, 0 logs all actions and # their durations, > 0 logs only # actions running at least this number # of milliseconds. #autovacuum_max_workers = 3 # max number of autovacuum subprocesses # (change requires restart) #autovacuum_naptime = 1min # time between autovacuum runs #autovacuum_vacuum_threshold = 50 # min number of row updates before # vacuum #autovacuum_analyze_threshold = 50 # min number of row updates before # analyze #autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum #autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze #autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum # (change requires restart) #autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age # before forced vacuum # (change requires restart) #autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for # autovacuum, in milliseconds; # -1 means use vacuum_cost_delay #autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for # autovacuum, -1 means use # vacuum_cost_limit これらの設定値を変更することによって、VACUUMの実行タイミングをコン トロール したりチューニングすることができます。 1. VACUUMが実行されるタイミングを知る 設定値でAUTO VACUUMの実行タイミングを調整することができます。 AUTO VACUUMは バキューム 閾値 を越えた場合実行されます。 バキューム 閾値 は以下の式で算出されます。 バキューム基礎閾値: #autovacuum_vacuum_threshold = 50 バキューム規模係数: #autovacuum_vacuum_scale_factor = 0.2 バキューム閾値 = バキューム基礎閾値 + バキューム規模係数 * タプル数 100万件のレコードを対象とした場合は バキューム 閾値 = 50 + 0.2 * 1000000 = 200050件 となります。 ※VACUUM実行のタイミングはautovacuum_naptimeで管理されます。 デフォルト設定値は60なので、60秒単位でAUTO VACUUMを実行するかどうかを監視しています。 実験 AUTO VACUUMの実行 実験前にVACUUMが実行されたかどうかの情報をチェックしておきましょう。 pg_stat_all_tables で確認することができます。 postgres=# SELECT * FROM pg_stat_all_tables WHERE relname = 'vacuum_test'; -[ RECORD 1 ]-------+------------ relid | 32812 schemaname | public relname | vacuum_test seq_scan | 0 seq_tup_read | 0 idx_scan | idx_tup_fetch | n_tup_ins | 0 n_tup_upd | 0 n_tup_del | 0 n_tup_hot_upd | 0 n_live_tup | 0 n_dead_tup | 0 n_mod_since_analyze | 0 last_vacuum | last_autovacuum | last_analyze | last_autoanalyze | vacuum_count | 0 autovacuum_count | 0 AUTO VACUUMが実行された回数 analyze_count | 0 autoanalyze_count | 0 autovacuum_count が0回になっているので、AUTO VACUUMは実行されていないことが分かります。 ついでに実ファイルも確認しておきましょう。 relidが32812とありますので、そちらが実ファイルの場所です。 実ファイルの場所は datid/relid です。以下の SQL でdatidを確認できます。 postgres=# SELECT datid, datname FROM pg_stat_database WHERE datname = 'postgres'; -[ RECORD 1 ]----- datid | 13461 datname | postgres postgres@ubuntu2004:~$ ls -lh /var/lib/postgresql/12/main/base/13461/32812* -rw------- 1 postgres postgres 0★ Dec 21 01:18 /var/lib/postgresql/12/main/base/13461/32812 実ファイルのサイズが0ですね。 続いて、レコード数がバキューム 閾値 を越えない場合はAUTO VACUUMが効かないことを確認します。 閾値 は20万50件なので、 閾値 を越えないよう20万件INSERTしてみます。 `INSERT INTO vacuum_test SELECT generate_series(1,200000),md5(clock_timestamp()::text);` -[ RECORD 1 ]-------+------------ relid | 32812 schemaname | public relname | vacuum_test seq_scan | 1 seq_tup_read | 200000 idx_scan | idx_tup_fetch | n_tup_ins | 200000 n_tup_upd | 0 n_tup_del | 0 n_tup_hot_upd | 0 n_live_tup | 200000 n_dead_tup | 0 n_mod_since_analyze | 0 last_vacuum | last_autovacuum | last_analyze | last_autoanalyze | 2022-12-23 05:06:47.194957+00 vacuum_count | 0 autovacuum_count | 0 ★カウントが増えていない analyze_count | 0 autoanalyze_count | 1  autovacuum_countが0のままですね。 実ファイルを確認してみましょう。 -rw------- 1 postgres postgres 14M★ Dec 21 01:20 /var/lib/postgresql/12/main/base/13461/32812 -rw------- 1 postgres postgres 24K Dec 21 01:20 /var/lib/postgresql/12/main/base/13461/32812_fsm 実ファイルが14Mに増加しています。 FSMが生成されていることも確認できますね。 続いてゴミデータを作るために1件を残して削除します。 DELETE FROM vacuum_test WHERE id <= 199999; `SELECT * FROM pg_stat_all_tables WHERE relname = 'vacuum_test';` -[ RECORD 1 ]-------+------------ relid | 32812 schemaname | public relname | vacuum_test seq_scan | 1 seq_tup_read | 200000 idx_scan | idx_tup_fetch | n_tup_ins | 200000 n_tup_upd | 0 n_tup_del | 199999 ★不要な行 n_tup_hot_upd | 0 n_live_tup | 1 n_dead_tup | 199999 n_mod_since_analyze | 199999 last_vacuum | last_autovacuum | last_analyze | last_autoanalyze | 2022-12-21 01:20:53.861522+00 vacuum_count | 0 autovacuum_count | 0 ★カウントが増えていない analyze_count | 0 autoanalyze_count | 1 n_tup_delが199999となっています。 こちらは不要な行を表していますので、先ほどDELETEしたレコードがゴミデータとなっています。 続いてバキューマの 閾値 (20万50件)を越えるレコードを追加すると、バキュームが実行されることを確認します。 あと50件以上レコードを作成すれば 閾値 を越えるためAUTO VACUUMが実行されるはずです。 ゆとりを持って100件追加してみましょう。 100件insert insert into vacuum_test select generate_series(1,100),md5(clock_timestamp()::text); auto_vacuumが実行されていることを確認 postgres=# SELECT * FROM pg_stat_all_tables WHERE relname = 'vacuum_test'; -[ RECORD 1 ]-------+------------------------------ relid | 32812 schemaname | public relname | vacuum_test seq_scan | 1 seq_tup_read | 200000 idx_scan | idx_tup_fetch | n_tup_ins | 200100 n_tup_upd | 0 n_tup_del | 199999 n_tup_hot_upd | 0 n_live_tup | 101 n_dead_tup | 0 n_mod_since_analyze | 0 last_vacuum | last_autovacuum | 2022-12-21 01:22:53.960128+00 last_analyze | last_autoanalyze | 2022-12-21 01:24:53.588466+00 vacuum_count | 0 autovacuum_count | 1 ★カウントが増えている analyze_count | 0 autoanalyze_count | 3 autovacuum_countが0から1に増加しています。 設定した 閾値 を越えたためAUTO VACUUMが実行されていますね。 実ファイルを確認してみます。 -rw------- 1 postgres postgres 14M★ Dec 21 01:27 /var/lib/postgresql/12/main/base/13461/32812 -rw------- 1 postgres postgres 24K Dec 21 01:20 /var/lib/postgresql/12/main/base/13461/32812_fsm -rw------- 1 postgres postgres 8.0K Dec 21 01:22 /var/lib/postgresql/12/main/base/13461/32812_vm VACUUMが実行されたため可視性マップも作成されていますね。 データファイルは圧縮されていないですが、不要領域が回収できており再利用できているかどうか確認します。 さらに20万レコードを追加 insert into vacuum_test select generate_series(1,200000),md5(clock_timestamp()::text); レコードを追加しても容量が14Mから変わっていません。 不要領域を回収して空き容量にリサイクルし、再利用出来ていることが確認できます。 -rw------- 1 postgres postgres 14M★ Dec 21 01:27 /var/lib/postgresql/12/main/base/13461/32812 -rw------- 1 postgres postgres 24K Dec 21 01:27 /var/lib/postgresql/12/main/base/13461/32812_fsm -rw------- 1 postgres postgres 8.0K Dec 21 01:27 /var/lib/postgresql/12/main/base/13461/32812_vm さらに20万レコードを追加 postgres=# insert into vacuum_test select generate_series(1,200000),md5(clock_timestamp()::text); INSERT 0 200000 postgres=# select count(*) from vacuum_test; -[ RECORD 1 ]- count | 400101 容量を確認します。 postgres@ubuntu2004:~$ ls -lh /var/lib/postgresql/12/main/base/13461/32812* -rw------- 1 postgres postgres 27M★ Dec 21 01:34 /var/lib/postgresql/12/main/base/13461/32812 -rw------- 1 postgres postgres 24K Dec 21 01:27 /var/lib/postgresql/12/main/base/13461/32812_fsm -rw------- 1 postgres postgres 8.0K Dec 21 01:27 /var/lib/postgresql/12/main/base/13461/32812_vm 再利用可能な空き容量が無くなったため追加したレコード分実ファイルが増加しました。 今回はVACUUMが実行されましたが、ある条件では実行されないケースがあります。 ロング トランザクション VACUUMが機能しない時、ロング トランザクション が原因になっているケースがあります。 ロング トランザクション とは、 トランザクション が開始されてから、コミット/ ロールバック が行われず長時間生存している トランザクション の事です。 ロング トランザクション よりも後の トランザクション 更新/削除されたレコードはバキューム処理の対象外となってしまいます。 ロング トランザクション バキュームが実行されたにも関わらずゴミデータが回収されていない場合は、ロング トランザクション があることを疑ってみましょう。 下記 SQL を実行すると、ゴミデータがいくら残っているかを確認できます。 SELECT relname, n_live_tup, n_dead_tup, round(n_dead_tup*100/(n_dead_tup+n_live_tup), 2) AS dead_ratio, pg_size_pretty(pg_relation_size(relid)) FROM pg_stat_user_tables WHERE n_live_tup > 0 ORDER BY dead_ratio DESC; -[ RECORD 1 ]--+------------ relname | vacuum_test n_live_tup | 1 n_dead_tup | 1100000 ★不要行。ゴミデータ dead_ratio | 99.00   ゴミデータが占める割合 pg_size_pretty | 72 MB ゴミデータが多く、VACUUM処理で不要領域が回収できていないことが考えられます。 ロング トランザクション が存在しているかどうかは下記 SQL で確認可能です。 SELECT pid, query, xact_start, state FROM pg_stat_activity WHERE state = 'idle in transaction'; pid | 1478 query | SELECT pid, query, xact_start, state FROM pg_stat_activity; xact_start | 2022-12-22 06:39:26.865784+00 state | idle in transaction★ stateが idle in transaction の トランザクション は、 トランザクション がスタートしているにも関わらずCOMMITもROLLBACKも行われていない状況です。 この トランザクション のxact_startの日時が古い場合はロング トランザクション と考えられるため、 pg_terminate_backend でプロセスの終了を実施します。 SELECT pg_terminate_backend(pid); 2. VACUUMのチューニングを実施する 頻繁にレコードが更新されるテーブルはメンテナンス負荷がかかるため、チューニングを実施するか自動バキュームの対象外にすることが望ましいです。 チューニング方法 auto_vacuumのログを確認してチューニングを行います。 auto_vacuum実行ログはデフォルトでOFFになっているため、下記 SQL でログ出力を行います。 VACUUMが設定したms以上かかった場合はログを出力します。 0に設定した場合はAUTO VACUUMの結果が出力され、-1では出力が無効になります。 log_autovacuum_min_duration = 60000ms ログは以下のように出力されます 2022-12-23 04:13:46.320 UTC [11751] LOG: automatic vacuum of table "postgres.public.vacuum_test": index scans: 1★ pages: 0 removed, 8334 remain, 0 skipped due to pins, 0 skipped frozen tuples: 999999 removed, 4 remain, 0 are dead but not yet removable, oldest xmin: 568 buffer usage: 44216 hits, 0 misses, 864 dirtied avg read rate: 0.000 MB/s, avg write rate: 4.742 MB/s system usage: CPU: user: 0.35 s, system: 0.03 s, elapsed: 1.42 s 2022-12-23 04:13:46.443 UTC [11751] LOG: automatic analyze of table "postgres.public.vacuum_test" system usage: CPU: user: 0.01 s, system: 0.00 s, elapsed: 0.12 s 特筆すべき点は、index scansです。 これはVACUUMの手順3(vacuuming indexes)が何回行われたかを指します。 今回は値が1なので1回行われたことになり、SCAN⇒VACUUMのループが発生しなかったと考えられますのでOKです。 これが1回以上であればループが発生していますので、vacuuming indexesを早く終わらせるための施策を実施する必要があります(maintenance_work_memを増やすなど)。 AUTO VACUUMが終わらない場合はまずココを疑ってみてチューニングをしてみましょう。 バキュームの実行時間を短くしたい システムのメンテナンス時間を短くしたいなど、できるだけ短時間でバキュームの実行を完了させたいケースになります。 また、VACUUMが実行されているにも関わらずテーブルが肥大化し続ける場合も有効です。 以下の方法がありますの状況に応じて組わせると良いかと思います。 バキュームの 閾値 を下げる autovacuum_vacuum_scale_factorを下げることでバキューム 閾値 が下がります。 例えばautovacuum_vacuum_scale_factorを0.2から0.01に下げると、 バキューム基礎閾値: #autovacuum_vacuum_threshold = 50 バキューム規模係数: #autovacuum_vacuum_scale_factor = 0.01 (100万行の場合) バキューム閾値 = 50 + 0.02 * 1000000 = 10,050件 となります。 デフォルトでは200,050件を越えなければVACUUMが実行されませんでしたが、ココを調整することで改善が望めます。 なお、 閾値 を下げすぎた場合は不必要なVACUUMによりオーバーヘッドが発生しますので注意が必要です。 バキュームの頻度を上げる autovacuum_vacuum_cost_delay を下げる(手動vacuum実行時はデフォルトで無効) 自動バキュームは autovacuum_cost_limitの設定値を処理すると、autovacuum_vacuum_cost_delayの設定値だけ一時停止します。 つまりautovacuum_vacuum_cost_delayを下げることで、vacuumの間隔を短くすることができ、早くVACUUMを終わらせることが可能になります。 なお、こちらを0にすることで全く遅延させることなく次のvacuum実行が可能になります。 maintenance_work_mem を増やす(最大1GB) maintenance_work_memを増やすことでVACUUM実行フェーズの内最も時間がかかる インデックスのバキューム を高速化します。 autovacuum_max_workers を増やす autovacuum_max_workersは、同時に実行することができるautovacuumプロセスの最大数です。 巨大なテーブルが存在する場合は増やすと改善が望めます。 任意のテーブルにバキュームの設定値をセットする postgresql .confの値を修正すると、全テーブルに対して設定を反映してしまいます。 そのため、各テーブルに必要なパラメータを設定するようにしましょう。 CASE 1: 任意のテーブルのみ頻繁/緩やかにVACUUMが実行されるようにする 任意のテーブルにパラメータをセットし、バキューム 閾値 をコン トロール できます。 ALTER TABLE vacuum_test SET (autovacuum_vacuum_threshold=50,autovacuum_vacuum_scale_factor=0.01); CASE 2: 任意のテーブルを自動バキュームの対象外にする 頻繁に更新が走るような巨大なテーブルは、システムが利用されていない夜間に手動でバキュームを行う運用方法も考えられます。 その場合は、下記の SQL で自動バキュームの対象外とすることができます。 ALTER TABLE vacuum_test SET (autovacuum_enabled = off); ALTER TABLE -[ RECORD 1 ]-------+------------------------------ relid | 32812 schemaname | public relname | vacuum_test seq_scan | 6 seq_tup_read | 800103 idx_scan | idx_tup_fetch | n_tup_ins | 600100 n_tup_upd | 0 n_tup_del | 200099 n_tup_hot_upd | 0 n_live_tup | 400001 n_dead_tup | 0 n_mod_since_analyze | 0 last_vacuum | last_autovacuum | 2022-12-21 01:25:53.554516+00 last_analyze | last_autoanalyze | 2022-12-21 01:34:54.471287+00 vacuum_count | 0 autovacuum_count | 2 analyze_count | 0 autoanalyze_count | 6 postgres=# DELETE FROM vacuum_test WHERE id <= 400000; -[ RECORD 1 ]-------+------------------------------ relid | 32812 schemaname | public relname | vacuum_test seq_scan | 6 seq_tup_read | 800103 idx_scan | idx_tup_fetch | n_tup_ins | 600100 n_tup_upd | 0 n_tup_del | 200099 n_tup_hot_upd | 0 n_live_tup | 400001 n_dead_tup | 0 n_mod_since_analyze | 0 last_vacuum | last_autovacuum | 2022-12-21 01:43:54.840929+00 last_analyze | last_autoanalyze | 2022-12-21 01:44:54.584522+00 vacuum_count | 0 autovacuum_count | 2 analyze_count | 0 autoanalyze_count | 6 autovacuum_countに変化がありません。 AUTO VACUUMの課題 定期実行が不可能 設定値を越えた場合にバキューム処理が実行されるため、システムが動いていない夜間にバキュームを実行させたいといったケースには対応できません。 空き領域をOSに返さない auto_vacuumでは不要領域を回収していることを確認しましたが、空いた領域をOSに返していません。 この場合、例えば、HDDの使用量によって課金請求が発生するため不要なデータを削除してHDD使用量を下げたいといったケースには対応できません。 こういったケースについては、VACUUM FULLを利用することで空き領域をOSに返すことができます。 VACUUM FULL 基本的な動きは以下の通りです。 テーブルのレコードを取得し新しいテーブルに詰め込む 新しいテーブルにインデックスを作成 テーブルを入れ替える 主な特徴は 不要領域を回収する 空き領域をOSに返す 実行時はテーブルロックがかかる テーブルの作り替えとインデックスの再作成を実施するため時間がかかる VACUUM実行時に古いテーブルと新しいテーブルが同時に作成されるため一時的に容量が辛くなる 利用シーンとしては、たとえばHDDの使用量によって課金請求するシステムであれば、ユーザーがシステム画面上で任意の時間にVACUUM FULLを使ったメンテナンスを実行するケースが思いつきます。 使い方 下記 SQL で実行可能です。 VACUUM FULL tablename; VACUUM FULLを行った場合は空き領域をOSに戻してくれるかどうかを確認してみましょう。 OSに空き領域を返してくれることを確認する その前に、実験用のテーブルはAUTO VACUUMの対象外にしておく必要があります。 ALTER TABLE vacuum_test SET (autovacuum_enabled = off); 実ファイルを確認しておきます。 postgres=# select relname, relfilenode from pg_class where relname = 'vacuum_test'; relname | relfilenode -------------+------------- vacuum_test | 32777 (1 row) postgres@ubuntu2004:~$ ls -lh /var/lib/postgresql/12/main/base/13461/32777* -rw------- 1 postgres postgres 0★ Dec 19 04:35 /var/lib/postgresql/12/main/base/13461/32777 ファイル容量は0ですね。 続いて100万件レコードを追加してみます。 postgres=# insert into vacuum_test select generate_series(1,1000000),md5(clock_timestamp()::text); INSERT 0 1000000 postgres@ubuntu2004:~$ ls -lh /var/lib/postgresql/12/main/base/13461/32777* -rw------- 1 postgres postgres 66M★ Dec 19 04:36 /var/lib/postgresql/12/main/base/13461/32777 -rw------- 1 postgres postgres 40K Dec 19 04:36 /var/lib/postgresql/12/main/base/13461/32777_fsm ファイル容量が66Mに増加しています。 大量にDELETEしてゴミデータを作成します。 postgres=# delete from vacuum_test where id < 999999; DELETE 999998 postgres@ubuntu2004:~$ ls -lh /var/lib/postgresql/12/main/base/13461/32777* -rw------- 1 postgres postgres 66M★ Dec 19 04:40 /var/lib/postgresql/12/main/base/13461/32777 -rw------- 1 postgres postgres 40K Dec 19 04:36 /var/lib/postgresql/12/main/base/13461/32777_fsm -rw------- 1 postgres postgres 8.0K Dec 19 04:40 /var/lib/postgresql/12/main/base/13461/32777_vm ファイル容量が減っていません。 VACUUM FULLを実行してみましょう。 VACUUM FULL vacuum_test; テーブルの再作成が行われるため、古いテーブルの実ファイルは空になっています。 postgres@ubuntu2004:~$ ls -lh /var/lib/postgresql/12/main/base/13461/32777* -rw------- 1 postgres postgres 0 Dec 19 04:46 /var/lib/postgresql/12/main/base/13461/32777 新しく作成されたテーブルの実ファイルを確認します。 postgres=# select relname, relfilenode from pg_class where relname = 'vacuum_test'; relname | relfilenode -------------+------------- vacuum_test | 32785 (1 row) postgres@ubuntu2004:~$ ls -lh /var/lib/postgresql/12/main/base/13461/32785* -rw------- 1 postgres postgres 8.0K★ Dec 19 04:46 /var/lib/postgresql/12/main/base/13461/32785 ファイル容量が減っていることが確認できます。 VACUUM FULLの課題 処理の重さ VACUUM FULLはテーブルとインデックスの再作成を行うため処理が非常に重くなります。 そのため、AUTO VACUUMでは本当に達成できないケースなのかを検討したうえでVACUUM FULLをせ択するようにしましょう。 排他ロック VACUUM実行中は強力な排他ロックがかかるため、システムが稼働していない時間帯にVACUUMの実行を終える必要があります。 排他ロック問題に関しては、 pg_repack と呼ばれる拡張があります。 こちらは、排他ロックを取らずにVACUUM FULL相当の処理を行ってくれる拡張となっていますので導入を検討してみるという手もあります。 終わりに VACUUMの機能と特徴を紹介させて頂きました。 デフォルト設定でも運用可能ですが、「DBの寿命はアプリケーションよりも長い」と言われるように、長期間運用を続けていくうちにレコードやゴミデータが溜まることで様々な問題が発生します。 PostgreSQL とは切っても切り離せないVACUUMの深淵を少し覗くことで、普段からこまめに掃除をしてくれているその仕組みと恩恵を知ることができ、さらに興味が湧いてきました! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
先日、JUnit4からJUnit5への移行作業を実施しました。 移行する際に色々とコードの変更が必要なのですが、作業しながら「パッケージと名前の変更点が一括でまとまってる記事があったらな~」と思ったのでこの記事でまとめることにしました。 アノテーション 変更されたもの 削除されたもの アサート 変更されたもの 削除されたもの おわりに 執筆 & 移行の際の参考サイト アノテーション 変更されたもの JUnit4 JUnit5 org. junit .Before org. junit .jupiter. api .BeforeEach org. junit .After org. junit .jupiter. api .AfterEach org. junit .BeforeClass org. junit .jupiter. api .BeforeAll org. junit .AfterClass org. junit .jupiter. api .AfterAll org. junit .Test org. junit .jupiter. api .Test 削除されたもの JUnit4 JUnit5 org. junit .runners.Enclosed org. junit .jupiter. api .Nested org. junit .Ignore org. junit .jupiter. api .Disabled org. junit .experimental.categories.Category org. junit .jupiter. api .Tag org. junit .runner.Runwith org. junit .jupiter. api .extension.ExtendWith org. junit .Rule org. junit .jupiter. api .extension.ExtendWith org. junit .ClassRule org. junit .jupiter. api .extension.ExtendWith ↓パラメータ化テスト (少し特殊なので別に記載) JUnit4 JUnit5 アノテーション (Runwithの引数に渡す) アノテーション org. junit .runners.Parameterized org. junit .jupiter.params.ParameterizedTest パラメータ指定 import org. junit .runners.Parameterized.Parameters org. junit .jupiter.params.provider.ValueSource org. junit .jupiter.params.provider.CsvSource etc... アサート 変更されたもの JUnit4 JUnit5 org. junit .Assert.assertTrue org. junit .jupiter. api .Assertions.assertTrue org. junit .Assert.assertFalse org. junit .jupiter. api .Assertions.assertFalse org. junit .Assert.assertEquals org. junit .jupiter. api .Assertions.assertEquals org. junit .Assert.assertArrayEquals org. junit .jupiter. api .Assertions.assertArrayEquals org. junit .Assert.assertNull org. junit .jupiter. api .Assertions.assertNull org. junit .Assert.fail org. junit .jupiter. api .Assertions.fail 削除されたもの JUnit4 JUnit5 org. junit .Assert.assertThat サードパーティー 製のライブラリを使う assertJ static org.assertj.core. api .Assertions.assertThat hamcrest org.hamcrest.MatcherAssert.assertThat Truth com. google .common.truth.Truth.assertThat おわりに 変更点盛りだくさんでした。 本記事がこれから移行する方の助けになれば幸いです。 執筆 & 移行の際の参考サイト https://speakerdeck.com/akkie76/koredeshi-bai-sinai-junit-5-hefalsemaiguresiyonfang-fa https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
ラク スでメールディーラーの運用サポートチームのリーダをやっています、@neroblubrosです。 この記事は ラク ス Advent Calendar 2022 の25日目のトリです。最近購入したキーボードの話をします。 手帳が置けないという問題 そうだセパレートタイプのキーボードにしてみよう! 欠点は矢印のキーが独自配列であること 手帳が置けないという問題 Todoやスケジュールは私は手帳に書いて記録しています。なので、パソコンで作業をするときも手帳を見ながら行います。 マイクロソフト のエルゴミックキーボードを使っていて、キーボードはすごく良いのですが、困ったことがあります。 それは、キーボードが長方形じゃないので意外と机上の場所を取るというのと、キーボードの前に手帳を置いて作業をしているのですが、それが使いづらいのです。 っていうか、そもそも手帳が机からはみ出しているし。 キーボードを使うときに手帳の上に自分のひじが乗っかって、打鍵がしにくかったり、気がつくとページがくしゃくしゃになったりして、なんとかしたいなーと思っていました。 そうだセパレートタイプのキーボードにしてみよう! ある日、同じチームの同僚がセパレートタイプのキーボードを使っていたのを見てひらめきました! セパレートタイプのキーボードにして、左右のキーボードの間に手帳を置けばええやん!! どうせなら自作して自分の気に入ったキーボードを作ろうと思いましたが、お値段がアレなので Mistel社のBAROCCO にしました。 軸は赤軸の静音です。キーボードに「高さ」があったので、12mmの パームレスト も買いました。 もちろん、 パームレスト もセパレートです。 これで手帳が置けなかったら本末転倒ですが、こんな感じ! 最高。 欠点は矢印のキーが独自配列であること 私にとって最高のキーボードなのですが、ひとつだけ欠点があります。それは矢印キーが独自の配列であることです。 矢印キーが一列に並んでいます。一般的にはこう↓ですよね。 このせいで打ち間違い多発。特に下を押しているつもりが上押してて、上と下をかなりの頻度で押し間違える。 なんとかならんかったんかな、これ。 矢印キーに少し難があるものの、机が広く使えるようになって快適なエンジニアライフを過ごしています。 皆さんがお使いのキーボードで良いキーボードがあれば教えてください。 あれはいいキーボードだ。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは、あるいはこんばんは。すぱ..すぱらしいサーバサイドのエンジニアの( @taclose )です☆ みなさん、 sshでパスワード不要にする記事 は読んでくださいましたか? 今回はその続編とも言える記事になります。 ネットで調べてるとどうも GitHub にログインする ssh の設定で苦労されてる内容をよく見かけます。 今一度何が正しくて、どんなエラーが出た時はどうしたら良いのか?を整理しました 。 誤った理解でそれっぽい設定しちゃってる方もおられるようなので、今一度振り返ってみてもらえればと思います。 まずはSSHの設定の基礎的な事(自信ある人は飛ばす!) GitHubのサーバ側の設定をしよう! まずは公開鍵の作成 GitHubに公開鍵を設定しよう! GitHub用に記載すべきssh/configはこれで決まりだ!! (1)どこに接続する時もこの設定 (2)Host __github.com* (3)個人用/会社用のGitHubへの接続設定 GitHubに接続できない!エラー別対応法 ssh: connect to host github.com port 22: Resource temporarily unavailable Permission denied (publickey). 秘密鍵や設定ファイルのパーミッションが良くない例 よくわからずUser名をgitじゃなくした例 いざgit cloneしたら出来ないんですけど! 最後に 参考文献 まずは SSH の設定の基礎的な事(自信ある人は飛ばす!) 2021年8月13日から GitHub でパスワードによる認証が廃止されました。 一応別口も用意されていますが、安全性や利便性を考えれば ssh でのログインに切り替える良いタイミングです! 以下の記事を参考にしながら、まずは ssh/config の基本的な記載方法と便利さを実感してください。 なんとなく理解出来てるよって人は、不明点があれば参考にしてくださいね。 tech-blog.rakus.co.jp では次へいきましょうっ GitHub のサーバ側の設定をしよう! まずは公開鍵の作成 今回はあえて個人用のアカウント(taclose)と会社用のアカウント(officer)がある前提で記載します。既に公開鍵作ってるよ!という方は公開鍵は使いまわしても問題ないのですが、さすがに個人用と仕事用は使い分けるので、まずは鍵の作成から入りましょう! 鍵の作成時に鍵の名前指定を忘れずに!id_ rsa ファイルを上書きしちゃうの!?って ssh -keygenに心配されちゃいますよ! # 個人開発用の鍵の作成 名前指定忘れずにね!(今回はid_rsa_tacloseとしました。自由に!) $ ssh-keygen -t rsa Generating public/ private rsa key pair. Enter file in which to save the key (/Users/(username)/.ssh/id_rsa): id_rsa_taclose # ここ! Enter passphrase (empty for no passphrase): # 何も書かずにEnter Enter same passphrase again: # 何も書かずにEnter # 鍵の移動(秘密鍵と公開鍵両方移動させましょう) $ cp -pr id_rsa_taclose* ~/.ssh/ # 権限を変更 秘密鍵のアクセス権は600に変更しましょう。 $ chmod 600 ~/.ssh/id_rsa_taclose # GitHubに公開鍵を持ってく必要があるのでクリップボードにコピーする。 $ pbcopy < ~/.ssh/id_rsa.pub # (Mac)ならこうっ $ clip.exe < ~/.ssh/id_rsa.pub # (Windows)ならこうっ $ cat ~/.ssh/id_rsa.pub # こんなのでも良い!(表示された内容を選択してCtrl+C) もし会社用の公開鍵も用意するなら、同じ要領で id_rsa_officer とか作って下さいね! GitHub に公開鍵を設定しよう! GitHub にアクセスしてログインしてください。 右上のユーザアイコンをクリックし、Settingsを選択 GitHub ユーザアイコンを選択してSettingsを選ぶ 左メニューの SSH and GPG keys を選択 SSH and GPG keysを選びましょう New SSH key ボタンを選択 New SSH keyを選択 タイトルはusername等適当な値でOKです。Key Type: Authentication Keyとし、先ほどコピーした公開鍵を張り付けて Add SSH key Add SSH key これでサーバ側の設定は完了です☆ GitHub 用に記載すべき ssh /configはこれで決まりだ!! まず答えを書きます! [root@A8630-LT keisuke.maeda]# cat ~/.ssh/config Host * // (1) ServerAliveInterval 60 IdentitiesOnly yes TCPKeepAlive yes Host __github.com.* // (2) Hostname ssh.github.com Port 443 User git Host __github.com.taclose // (3) IdentityFile ~/.ssh/id_rsa_taclose Host __github.com.officer // (4) IdentityFile ~/.ssh/id_rsa_officer (「あ〜なるほどね、うんうん」という人は次にいきましょうっ) 解説します。 (1)どこに接続する時もこの設定 Hostって部分が俗にいう エイリアス (別名)をつける部分ですが、* や ?とかが指定可能です。 Host * みたいに 正規表現 で書いておけば、どのHost設定にも共通の設定として記載できます。 なので、 「 SSH の接続がぷつぷつ切れて困ってるんです」 とか 「 秘密鍵 を使い分けてるんですが、どうもうまくいかない」 そういう人は共通設定にこれ入れておけば安心ですね! (2)Host __ github .com* (1)の説明と重複しますが、こんな風に定義しておけば 『__ github .com~みたいな設定は共通でこういう定義ね!』と出来ます。 私の作業するNWでは github .comに対してport 22番での通信が出来ませんので、 Hostname ssh.github.com Port 443 という定義を GitHub に接続する共通の設定にしていますが、ここは以下のようにする人も多いかもしれません。 Hostname github.com Port 22 後半によくあるエラーの対策方法の説明があるので、そこで詳細に触れましょう!今は 「 GitHub への ssh 接続で22ポート使える人とそうじゃない人がいるのか。 GitHub は443ポートでもできるんだ。」 ぐらいで思っておいてもらえればOKです。 (3)個人用/会社用の GitHub への接続設定 よく見かける ssh の設定方法の説明だと1つのHostに対して何度もHostname, port, User等を定義しているのを見かけますが、共 通化 して書く事でここまで綺麗にかけるんですね! でも 秘密鍵 を使い分けてるだけで本当にアカウント使い分けできてるの?と思われるでしょう。 以下がコマンドの実行例です。 [root@A8630-LT keisuke.maeda]# ssh -T __github.com.taclose Hi taclose! You've successfully authenticated, but GitHub does not provide shell access. [root@A8630-LT keisuke.maeda]# ssh -T __github.com.officer Hi kmaeda-rakus! You've successfully authenticated, but GitHub does not provide shell access. おお! ssh での接続テストをしてみたところ、 Hi taclose! とか Hi kmaeda-rakus! と呼び分けてくれてますね! 秘密鍵 に紐づいたアカウントを自動で選択してくれているわけですね! 実際 __ github .com* の設定をみても、 User git としており、tacloseなんてうたってないわけですが、こんな結果になるわけです。 すぱ..すぱらしい!!エレガント! GitHub に接続できない!エラー別対応法 さて、では接続出来なかった人のためによくあるケースを元に対策を記載まとめていこうと思います。 と、その前に接続テストの方法は以下になります。 ssh /configの記載は各自の内容に読み替えて実施してくださいね! 私の場合は以下になります。 # ssh/configでUserやHostname設定してあるのでこうなる # 意味としては「ssh -T git@ssh.github.com -i ~/.ssh/id_rsa_taclose -p443」これと同じ意味になります。 $ ssh -T __github.com.taclose # 接続うまくいかない時は -vTにすると原因がわかりやすい事もありますよ!エラー例も載せておきます。 # これをみたらport何番使おうとしてるのか?とかがよくわかりますね!設定ミスにも気づけます。(わざとport 22使ってエラー出してます) $ ssh -vT __github.com.taclose OpenSSH_7.4p1, OpenSSL 1.0 .2k-fips 26 Jan 2017 debug1: Reading configuration data /root/.ssh/config debug1: /root/.ssh/config line 1 : Applying options for * debug1: Reading configuration data /etc/ssh/ssh_config debug1: /etc/ssh/ssh_config line 58 : Applying options for * debug1: Connecting to github.com [ 20.27 . 177.113 ] port 22 . debug1: connect to address 20.27 . 177.113 port 22 : Resource temporarily unavailable ssh: connect to host github.com port 22 : Resource temporarily unavailable ssh : connect to host github .com port 22: Resource temporarily unavailable このエラーが出た人は今いるNW環境が GitHub に対するport 22が許可されていない事が考えられます。 私もこれに該当していたのでそんな人は私が先ほども記述していた以下のような設定を試して下さい。 以下の設定は GitHub に443ポートでアクセスする設定です。 Host __github.com.* Hostname ssh.github.com # ここがgithub.comじゃないよ! Port 443 # ここが22じゃないよ! User git Permission denied (publickey). これは一番よくみるエラーかもしれません。 秘密鍵 や設定ファイルの パーミッション が良くない例 # 正しいパーミッション (userやgroupの列は気にしないで下さいね!私はWSLでさくっと環境準備したのでrootになってます。) $ ls -al ~/.ssh/ drwx------ 1 root root 512 12 月 19 19 : 06 . -rw------- 1 root root 1904 12 月 19 19 : 06 config ... -rw------- 1 root root 1675 3 月 3 2022 id_rsa_taclose ... # 設定ずれてる方は以下のコマンドを実行しましょう $ chmod 700 ~/.ssh # .sshフォルダは700 $ chmod 600 ~/.ssh/config # 設定ファイルは600 $ chmod 600 ~/.ssh/id_rsa_taclose # 秘密鍵は600 よくわからずUser名をgitじゃなくした例 たまにあるのが、Userという設定項目だから無条件にgitという値を使わずにtacloseとかにしちゃう例です。例えば以下。 # taclose@じゃないよ!git@だよ!エラーになるよ! $ ssh -T taclose@ssh.github.com -i ~/.ssh/id_rsa_taclose -p443 Permission denied (publickey). ssh の接続では常にユーザはgitです!お忘れなく もしここまできてもこのエラーが治らない人は -vT で詳細な理由を確認してみてください。 実はconfigファイルに記載した 秘密鍵 のファイルパスが間違ってるだけでも以下のようになります。 $ ssh -T __github.com.taclose no such identity: /root/.ssh/id_rsa_taclose2: No such file or directory Permission denied (publickey). 悩んだらまず -vT お忘れなく! いざgit cloneしたら出来ないんですけど! GitHub にアクセスして、 リポジトリ を確認したらこんな風になってるはずです。 GitHub の リポジトリ ( SSH ) じゃ、Cloneするかな!って実行すると・・・? # git clone git@github.com:taclose/mkmotd.git Cloning into 'mkmotd' ... ssh: connect to host github.com port 22 : Resource temporarily unavailable fatal: Could not read from remote repository. はい、こけちゃいます。 ssh /configに設定したのはあくまで __github.com.taclose なので以下のように書けばいけます 。 # git clone __github.com.taclose:taclose/mkmotd.git Cloning into 'mkmotd' ... remote: Enumerating objects: 13 , done . remote: Counting objects: 100 % ( 13 / 13 ), done . remote: Compressing objects: 100 % ( 8 / 8 ), done . remote: Total 13 (delta 2 ), reused 8 (delta 2 ), pack-reused 0 Receiving objects: 100 % ( 13 / 13 ), 4.07 KiB | 0 bytes/s, done . Resolving deltas: 100 % ( 2 / 2 ), done . 『でも、デフォルトでいきたいんですけど!』 って人は ssh /configの設定を以下のように修正するといけますよ。 github .comのデフォルトを追加 Hostの所には半角スペース区切りで複数定義できます。なので、 github.com と言われれば __github.com.taclose の設定を使ってね ってしたわけですね! これでさっきのコマンドを打つと・・・? [root@A8630-LT tmp]# git clone git@github.com:taclose/mkmotd.git Cloning into 'mkmotd' ... remote: Enumerating objects: 13 , done . remote: Counting objects: 100 % ( 13 / 13 ), done . remote: Compressing objects: 100 % ( 8 / 8 ), done . Receiving objects: 100 % ( 13 / 13 ), 4.07 KiB | 0 bytes/s, done . Resolving deltas: 100 % ( 2 / 2 ), done . remote: Total 13 (delta 2 ), reused 8 (delta 2 ), pack-reused 0 成功! 最後に ssh 接続失敗系のエラーはたくさんありますが、 GitHub の場合はサーバ側は GitHub がよしなにやってくれてます。だから、案外コピペミスとかNW設定周りぐらいしか原因はないです。 ただ、 Mac ユーザにとっては当たり前ですが、 ssh -addが必要とかはあるので、そういった点は参考文献にも記載しましたが GitHub Docs: SSH のトラブルシューティング ここでも解説されてるものがあります。公式の トラブルシューティング なので見てみると良いかもしれませんね! エラーに屈せず、勉強の機会にしちゃいましょう!お疲れ様でした! 参考文献 実践sshコマンド:基本からオススメの設定 / ノウハウをまとめたよ! - RAKUS Developers Blog | ラクス エンジニアブログ GitHub Docs: SSH のトラブルシューティング エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
はじめに 皆さんこんにちは。インフラエンジニアやってますmoja_chiroです。 今回は Python と Selenium を使用してちょっとした自動化を行い、業務効率UPを目指しました。 日々の運用業務の中で月次集計のレポート作成で手間と時間が取られていたため、データの集計作業(コピペ)を自動化させて楽になりました。帰宅前に Python プログラムを実行しておくと、翌日出社したタイミングではエクセルに結果が保存されているという内容です。 一部 Selenium ではなく、PyAutoGUIも使用しました。 一部のコードを記載しておりますが、あくまで個人の業務効率UPを目的としているため、コードの不備はあらかじめご了承ください。 はじめに Pythonとは Seleniumとは Pythonをインストール Seleniumをインストール WebDriver(ChromeDriver)のインストール Seleniumの基本的な使い方(Googleでの検索結果の取得と保存) 要素の取得 要素の操作 ブラウザの要素確認 ログインページのサンプル XPathによる検索 今後の発展 さいごに Python とは Python とは、組み込み開発、WEBアプリケーション、デスクトップアプリケーションなどで利用されるプログラム言語です。 インフラ畑の人間からすると、プログラム言語っていうと敷居が高いイメージです。 Python を検索すると、 人工知能 や 機械学習 の分野やWeb アプリ開発 でよく利用されており人気の言語らしいです。 言語としての特徴は、文法がとてもシンプルで、標準ライブラリや外部ライブラリが多く、初心者でも扱いやすい言語です。 ▼公式ドキュメント www.python.org ▼ Wiki ペディア ja.wikipedia.org Selenium とは Selenium とは ブラウザー の自動化を可能にし、ブラウザを自動的に操作するツールとライブラリーです。 ▼ Selenium の公式ページ www.selenium.dev Python をインストール まず始めに Python の環境を構築する必要があります。 ▼公式ページ www.python.org Python 公式サイトから、 Python パッケージをダウンロードします。 ダウンロードしたパッケージをインストールします。 PowerShell で Python を実行するときに必要となる、 スクリプト の実行許可を設定します。 ダウンロードからインストール手順はこちらのサイトが分かりやすいと思います。 ▼ Windows 版 Python のインストール www.python.jp ウィザードの中で出てくるパスを通しておく必要があります。   ※ Add python.exe to PATH のチェックを入れる また、開発するにあたっては Python 実行環境を構築する必要があります。 次のコマンドを入力して、仮想環境を構築します。 C:\Users\user1\sample1>python -m venv virtual_environment ・仮想環境への切替 Windows の PowerShell を使用する PS C:\Users\user1\sapmle1> PS C:\Users\user1\sapmle1> virtual_environment\Scripts\activate.ps\Scripts\activate.ps 仮想環境を終了する場合は、 deactivate コマンドを実行します。 Selenium をインストール ・pipを使用して Selenium をインストールする pipとは、The Python Package Indexで公開されている Python パッケージのインストールなどを行うユーティリティで、 Python 3.4以降では標準で付属されています。 パッケージのインストールは、pipのinstallコマンドで行います。 Selenium パッケージをインストールするときは、次のように実行します。 C:\Users\> python -m pip install selenium インストール時に SSL のエラーが発生する場合は、trusted-hostのオプションを付けるか、pip.iniファイルを作成して実行してください。 C:\ProgramData\pip\pip.ini pip.iniの中身 [global] trusted-host = pypi.python.org pypi.org files.pythonhosted.org WebDriver(ChromeDriver)のインストール Selenium では、WebDriverを使用してブラウザを操作します。そのため Selenium を使用するためにはWebDriverのインストールが必要です。 WebDriverは、使用するブラウザの種類によって使い分けが必要で、今回は使い慣れている「 Google Chrome 」のドライバー(ChromeDriver)を使用します。 ChromeDriverのインストールについては、以下のリンクからDriverをダウンロードしてください。 インストールするバージョンは、現在使用中の「 Google Chrome 」のバージョンに合わせます。 ▼ ダウンロードサイト sites.google.com chrome ドライバーは作業用フォルダーと同じ場所に保存する方が管理しやすいです。 保存先の ディレクト リを指定する場合は、 executable_path="" で任意のパスを指定する必要があります。 指定する場合は以下のように記述します。 from selenium import webdriver from selenium.webdriver.chrome.service import Service driver_path = r"\C:\python\selenium\web_driver\chromedriver.exe" service = Service(executable_path=driver_path) driver = webdriver.Chrome(service=service) Selenium の基本的な使い方( Google での検索結果の取得と保存) ではまず手始めに、 Google のページを3秒間表示してブラウザを閉じてみます。 表示させたいURLを driver.get("URL") に記述します。 import time from selenium import webdriver from selenium.webdriver.chrome.service import Service driver_path = r"\C:\python\selenium\chromedriver.exe" service = Service(executable_path=driver_path) driver = webdriver.Chrome(service=service) driver.get( "https://www.google.co.jp/" ) time.sleep( 3 ) driver.quit() 「 chrome は自動テストソフトウェアによって制御されています。」というメッセージが表示されています。 自動的にブラウザが閉じれば成功です。 要素の取得 Selenium で Webブラウザ の自動化をするためには、そのブラウザ内で使用されている要素を取得する必要があります。 WebDriverには標準のロケータが8種類あります。 以下の8種類の要素に対して実行したい操作を記述していくという流れで自動化を進めます。 ロケータ 詳細 class name class名に値を含む要素を探す (複合クラス名は使えない) css selector CSS セレクタ が一致する要素を探す id id属性が一致する要素を探す name name属性が一致する要素を探す link text a要素のテキストが一致する要素を探す partial link text a要素のテキストが部分一致する要素を探す tag name タグ名が一致する要素を探す xpath XPath と一致する要素を探す ページ内の要素が複数ある場合は、複数系の「s」がついたメソッドを利用します。 取得した内容はリストとして扱われます。 要素の操作 要素に対して実行できるコマンドは次の5つです。 click (どの要素にも実行可能) send keys (テキスト フィールドとコンテンツの編集可能な要素にのみ実行可能) clear (テキスト フィールドとコンテンツの編集可能な要素にのみ実行可能) submit (フォーム要素にのみ実行可能) select (リスト要素の選択で実行可能) ※ ※selectは要素のタイプによって動作が異なります。詳しくは、 Selenium のドキュメントを確認ください。 www.selenium.dev ブラウザの要素確認 実際にWEBページの取得したいデータの場所を特定し、elementを取得する必要があります。 ここが大変ですが、「 Google Chrome 」の デベロッパ ーツールで確認できます。 「 Google Chrome の縦 三点リーダ ー」→「その他のツール」→「 デベロッパ ー ツール」を押下、またはショートカットキーの「F12」もしくは「Ctrl+Shift+I」で起動します。 左上の要素選択モードのアイコンをクリックします。ショートカットは、「Ctrl+Shift+C」です。 ログインページのサンプル 実際に Python と Selenium を使用して自動化を行ったログインページです。 ユーザー名の要素確認結果 パスワードの要素確認結果 ユーザー名とパスワードの値を入力して自動ログイン # アイテムの取得 login = driver.find_element_by_name( 'userName-inputEl' ) password = driver.find_element_by_name( 'password-inputEl' ) # テキスト送信 login.send_keys( "admin" ) password.send_keys( "password" ) # ボタンのクリック driver.find_element_by_id( "loginBtn-btnEl" ).click() XPath による検索 要素の特定が難しい場合は、直接パスを指定することも可能です。 要素を選択した状態で、右クリック「copy」を選択することで XPath をコピーできます。 ▼ XPath の取得 //*[@id="loginBtn-btnInnerEl"] ▼full XPath の取得 /html/body/div/form/div[3]/a/span/span/span[2] 今後の発展 現在、ブラウザの表示とデータの収集までできるようになったので、取得したデータの加工も自動化できるように勉強中です。 Python の Selenium 以外のツールを利用して、他の定型業務も自動化していきたいと考えております。 また、機会があればその内容をまとめてみたいと思います。 さいごに Python と Selenium を使用したWeb スクレイピング の話を紹介しました。 みなさんも自動化で業務の効率化をされてはいかがでしょうか。 ただし、 データ取得先のサーバーへの過度な負荷をかけないこと 利用規約 を守ること 著作権法 を守ること について、くれぐれもご注意ください。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
はじめに こんにちは、 id:FM_Harmony です。 今回は iOS アプリの開発で実践した Xcode でのlldbを使った デバッグ 事例 について、 3件ほど紹介したいと思います。 lldbを使った デバッグ は ブレークポイントで処理を止めて、変数を読み書きする 位かと思っていましたが、 他にもいろいろなことが出来ると知ったので、 iOS アプリ開発 のTIPS(ノウハウ/テクニック)として紹介します。 はじめに lldbとは lldbを使ったデバッグ事例 アプリアイコンのバッジを付与する デバッグ方法 活用例 UserDefaultsを読み書きする デバッグ方法 活用例 アプリに保存したCookieを扱う デバッグ方法 活用例 余談:ブレークポイントで止まらない事例 終わりに lldbとは 前回私が投稿した記事でも紹介しましたが、 Xcode にはlldbというデバッガが入っています。 tech-blog.rakus.co.jp 普通は Xcode で ブレークポイント を貼ることで処理を止めて、lldbを使って変数の確認や操作ができますが、 ビルドの設定等によってはそれが出来ないことがあります。 一時的に設定を変えて ブレークポイント で処理が止まるようにもできますが、 動作確認したいことによっては、設定を変えずにlldbを使って デバッグ できる事があります。 lldbを使った デバッグ 事例 アプリアイコンのバッジを付与する デバッグ 方法 プッシュ通知機能がアプリに実装されている場合、 受信した通知に合わせて、アプリアイコンにバッジを付与することができます。 アプリアイコンバッジの例 iOS の場合、通知内のパラメータでバッジを付与することが出来ますが、 アプリからもコードでバッジを付与することが出来ます。 そのため、 ブレークポイント で処理を止めなくても、 Xcode から処理を止めた上で、 以下のコードをlldbで実行することで、任意のタイミングでバッジを付与することができます。 (lldb) expression - l swift -- import UIKit // アプリアイコンのバッジ件数を変更する (lldb) expression - l swift -- UIApplication.shared.applicationIconBadgeNumber = /**【アプリアイコンのバッジ件数(数値)】**/ ; expr は式を評価するコマンドで、1行目でUIKitの フレームワーク を読み込み、 2行目でアプリアイコンのバッジを付与します。 注意点としては、処理を止めた箇所によってはSwiftのコードが実行できないため、 expr -l swift で実行する言語にSwiftを指定する必要があります。 但し、 Objective-C のコードであれば、言語を指定することなく、 そのまま実行することが可能です。 また、 ブレークポイント で処理を止めた箇所によっては、 UIKitの フレームワーク は読み込まれていない可能性があります。 その場合はバッジを付与する前に読みこむ必要があります。 活用例 例えば、アプリアイコンのバッジ件数が期待値となる動作を行いたい場合、 バッジ件数を確認前の状態に戻すことができるので、とても便利です。 楽楽精算が提供しているiOSアプリ は、プッシュ通知機能を提供しており、 アプリからログアウトすることで、それまで付与されていたアプリアイコンのバッジの件数が0件になります。 実際の動作確認では、都度ログアウトする必要があったため、 コマンド1回実行することでアプリアイコンのバッジ件数を戻すことができ、楽に動作確認をすることができました。 UserDefaultsを読み書きする デバッグ 方法 アプリアイコンのバッジのように、UserDefaultsもlldbからコマンドを実行して、 値を読み書きすることが出来ます。 (lldb) expression - l swift -- import Foundation // キー:fugaに対して値:hogeをUserDefaultsに書き込む (lldb) expression - l swift -- UserDefaults.standard. set ( "hoge" , forKey : "fuga" ) // キー:fugaに対する値をUserDefaultsから読み取る (lldb) expression - l swift -- UserDefaults.standard.string(forKey : "fuga" ) 1行目で必要な フレームワーク を読み込み、2行目以降で操作する流れは、 アプリアイコンのバッジの例と同じです。 活用例 UserDefaultsに保存した情報によっては、アプリの操作で値が変更できないことがあります。 例えば「新バージョンでは利用しなくなった過去バージョンの設定値が残っていた時に何か処理を行う」 といった場合です。 具体的に、旧バージョンではUserDefaultsにある機能の設定値を格納していたが、 新バージョンでその機能は廃止し、設定値が残っていればお知らせを表示するといった場合を考えます。 お知らせが表示されるかを確認する場合、廃止した設定値を画面から操作することはできないため、 旧バージョンで設定値を変更して新バージョンへアップデートする方法があります。 しかし、アップデート前後のアプリを ipa などで用意して、都度アップデートするのは手間が掛るため、 上記の方法でUserDefaultsの値を直接書き換えた方が楽でした。 アプリに保存した Cookie を扱う デバッグ 方法 lldbから HTTPCookieStorage を利用することで、 アプリに保存した Cookie を デバッグ 中に扱うことができます。 (lldb) expression - l swift -- import Foundation (lldb) expression - l swift -- let $storage = HTTPCookieStorage.shared // 名前:hogeに合致する最初のCookieを取得 ※例示のため強制アンラップしている (lldb) expression - l swift -- var $cookie = $storage .cookies ! .first{ $0 .name == "hoge" } ! // 取得したCookieの値を確認 (lldb) expression - l swift -- print( $cookie ) // 取得したCookieをアプリから削除 (lldb) expression - l swift -- $storage .removeCookie( $cookie ) 1行目で必要な フレームワーク を読み込んで、以降の行で処理を行う流れはこれまでと同じです。 上記で紹介したコマンドと異なる点は、2行目、4行目でコマンドの結果を変数に格納しているところです。 lldbでは先頭に $ マークを付けると、以降の デバッグ で変数として利用可能になるので、 HTTPCookieStorageや Cookie を変数に格納して、 Cookie の確認や削除を行っています。 活用例 アプリに正しく Cookie が保存されたか、 Cookie の有無で変わる挙動を確認するのに役立ちました。 WebViewを利用した画面では、 Cookie を利用することがありますが、 その Cookie はアプリが持つ Cookie とは別に保存されます。 そのため、WebViewへのアクセスで Cookie が更新されたとき、 更新された Cookie をアプリへ同期する必要があります。 例えば、 safari のWebインスペクタを利用すればWebViewが利用している Cookie は確認できますが、 実際にアプリが利用している Cookie は確認することができないため、 上記の デバッグ 方法により正しい Cookie が保存されたか確認できる点で非常に有効です。 余談: ブレークポイント で止まらない事例 今回紹介した事例はいずれも ブレークポイント ではなく、 Xcode の Pause program execution の機能を使って、 処理を止めた際の デバッグ 方法です。 ブレークポイント で処理を止めた際は、実装されているコードを操作できるので、 フレームワーク の読み込みやUserDefaults、HTTPCookieStorageの操作は楽です。 では、どんな時に ブレークポイント を使えないのかというと、 例えば、releaseビルドで作成した ipa では ブレークポイント が破線になってしまい、 利用することができませんでした。 終わりに いかがでしたでしょうか。 個人的な感想ですが、これまで po コマンドで変数を確認するくらいしかlldbを利用していなかったので、 実行する言語の指定や変数の宣言、値の格納といったことまでlldbで行えることに驚きました。 今回の記事が、 iOS アプリ開発 に携わる皆様のお役に立ちましたら幸いです。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは、あるいはこんばんは。すぱ..すぱらしいサーバサイドのエンジニアの( @taclose )です☆ みなさん前回の記事で ssh のログインは楽に出来るようになりました! 前回の記事:  実践sshコマンド:基本からオススメの設定 / ノウハウをまとめたよ! - RAKUS Developers Blog | ラクス エンジニアブログ しかし!ここで問題になるのが安易にログインできるが故に間違ってログインしちゃう問題です。 そこで今回は間違ってログインしないように ログインしたら好きなキャ ラク ターにおはやいます! してもらいましょう! これで ログインしたサーバが自分用の開発サーバなのか、真面目にやってる検証環境なのか一目瞭然 ですね! ログインした時の画像を準備しよう! 画像をテキストデータに変換しよう! サーバに設定しよう! ログインしてみよう! 付録 画像をもっと加工したい! コマンドのオプション 終わりに 参考文献 ログインした時の画像を準備しよう! 手順は追って説明しますが、準備さえ整えば以下のようにコマンド1個で画像が作れて、アップロードもコマンド1つだけです! ssh でログインした時に表示される画像 著作権 上アニメのキャ ラク ターとかはこのブログでは扱えませんので、今回は以下のサイトから画像を準備しました。 無料ドット絵アイコン素材のピクセルガロー|ドット絵アイコンが使い放題 皆さまも子供の写真だったり「DEV」って書かれただけの画像でも構いませんのでご準備をっ 画像をテキストデータに変換しよう! コンソールには当然ながら画像を表示する機能なんてありませんので、一旦用意した画像を変換する必要があります。本当は「画像サイズを小さくして、背景色を黒にして。。。」と手順が大変なのですがコマンドを用意しましたので、以下の手順に沿って作業を進めてください! # ImageMagickをインストールする $ yum install -y ImageMagick 下記のコマンドでmkmotdコマンドをダウンロードしてください。 $ wget --no-check-certificate "https://raw.githubusercontent.com/taclose/mkmotd/main/mkmotd" -O mkmotd ※ wget コマンドを使えないよ!という方は以下の スクリプト を mkmotd とか、好きな名前で保存してください! #!/usr/bin/bash # ImageMagickが使えるか? if !( type "convert" > /dev/null 2 > & 1 ); then # 対象のコマンドをインストールするような処理 echo "please install ImageMagick!!" echo "$ yum install -y ImageMagick" exit 1 ; fi #定数 tmp= "tmp/temp_image" ; dot_path= "tmp/dot_image" # DEFAULT path= "" size= "32" trans_position= "0,0" background= "0;0;0" # 環境準備 mkdir -p tmp # OPTION GET while getopts p:s:t:b: - : opt; do optarg= " $OPTARG " [[ " $opt " = - ]] && opt= " $OPTARG " && optarg= " ${!OPTIND} " && shift case $opt in p | path ) path= " $optarg " ; ;; s | size ) size= " $optarg " ; ;; t | trans-position ) trans_position= " $optarg " ; ;; b | background ) background= " $optarg " ; ;; * ) echo " $0 : illegal option -- ${opt##-} " > & 2 ; exit 1 ;; esac done # CONFIRM echo "path= $path " echo "size= $size " echo "trans_position= $trans_position " echo "background= $background " trans_position=( ${trans_position//,/ } ) transparent_after= " $background " # URLなら画像を取得する if [[ $path =~ ^http ]] ; then echo "TASK: Download start." tmp_name= `basename " $path " ` wget --no-check-certificate " $path " -O " $tmp_name " > /dev/null 2 > & 1 path= " $tmp_name " echo "new path= $path " fi # 透過したい色を取得する echo "TASK: Transparent color get." rgb= `convert " $path " -crop 1x1+${trans_position[0]}+${trans_position[1]} txt: - | tail -n1 | awk '{print $3}' ` echo $rgb trans_r= `bc <<< "obase=10; ibase=16; ${rgb:1:2} " ` trans_g= `bc <<< "obase=10; ibase=16; ${rgb:3:2} " ` trans_b= `bc <<< "obase=10; ibase=16; ${rgb:5:2} " ` transparent_before= " ${trans_r} ; ${trans_g} ; ${trans_b} " echo "get trans_color: $transparent_before " # リサイズ処理をする echo "TASK: Resize image." convert " $path " -interpolative-resize ${size} x ${size} -compress none " $tmp " ppm= $( convert " $tmp " -interpolative-resize ${size} x ${size} -compress none ppm: - 2 > /dev/null ) # get cols, then discard ppm parameters set ${ppm} cols= ${2} shift 4 { # get three each values (rgb) from ppm for i in $( eval echo { 1 .. ${#} .. 3 } ) do j= $(( i +1 )) k= $(( i +2 )) rgb= " ${!i} ; ${!j} ; ${!k} " # replace transparent color if [ ${rgb} == ${transparent_before} ] ; then rgb= ${transparent_after} fi # start a new line when reach cols if [ $(( ( ${i} + 2) / 3 % ${cols } )) -eq 0 ] ; then echo -e " \0 33[48;2; ${rgb} m \0 33[m" else echo -en " \0 33[48;2; ${rgb} m \0 33[m" fi done } > $dot_path cat $dot_path echo "COMPLEATE!! Let's Setting!!" echo -e " \e [34;1m$ scp $dot_path {your-name}@{your-server}:/etc/motd \e [m" exit 0 いざ、変換処理を実行! # 実行権限ないなら $ chmod +x mkmotd を忘れずに! $ ./mkmotd -p tmp/military_totsugeki.gif 最後にscpコマンドが出たら成功です!ローカルに tmp/dot_image というファイルが出来上がってるはずです。 サーバに設定しよう! 最後に出てきたscpコマンドのようにしてもらえれば完成です! # your-name, your-serverは各自のユーザ名やドメイン名にしてくださいね $ scp tmp/dot_image your-name@your-server:/etc/motd # 前回の記事でログインを簡易化した方は以下のようなコマンドにしてもOKです $ scp tmp/dot_image __taclose.develop:/etc/motd ログインしてみよう! よし!ログインしてみましょう! ログイン成功! 感動ですね! 付録 画像をもっと加工したい! 今回用意した スクリプト では画像サイズや背景色とかを選べるように作ってありますので、以下のオプション指定の例を参考に試してみてください。 // 一番ミニマムな例 画像パスだけ指定 $ mkmotd.sh -p "logo.png" // 一番ミニマムな例 画像URLだけ指定 $ mkmotd.sh -p "https://www.rakus.co.jp/images/common/favicon.png" // フルで指定(デフォルト値) $ mkmotd.sh --path "logo.png" --size 32 --trans-position 0 , 0 --background "0;0;0" // サイズは48px、左上から 5 , 5 の座標の色が背景色、この背景色を赤にする $ mkmotd.sh --path "logo.png" --size 48 --trans-position 5 , 5 --background "255;0;0" 各種オプション指定の意味は以下のようになっています。 コマンドのオプション option default 説明 -p, --path 無し(必須指定) ファイルパスかhttpから始まるURLを指定してください。http指定の場合は wget コマンドも必要です。 -s, --size 32 画像サイズを変更します。コンソール表示を考えると16~64辺りが妥当です。 -t, --trans-position 0,0 左上からの座標を指定。指定された座標にある色を背景色として変更対象とします。 -b, --background 0;0;0 背景色と指定された色をRGBで指定された色に変換します。0~255でRGBを指定してください。ex:赤なら"255;0;0"です。 終わりに 画像が可愛いものを使っているのでちょっとゆるい感じになりましたが、 ログインしているサーバに間違いがないかを意識せずともクリアできるというのは案外大事 な事です。 人間は文字を読まずにアイコンだけを見て操作している事って非常に多いので、皆さんも仕事のモチベーションアップのためにも「好きなキャ ラク ターにおはやいます!」してもらってくださいね! よし、私も好きなキャ ラク ターを選び直そう! 参考文献 GitHub - taclose/mkmotd: sshでログイン時に表示するmotdのドット絵作成ツール 実践sshコマンド:基本からオススメの設定 / ノウハウをまとめたよ! - RAKUS Developers Blog | ラクス エンジニアブログ sshログインした時に任意の画像を出現させるようにするのをDockerでしてみた - Qiita 無料ドット絵アイコン素材のピクセルガロー|ドット絵アイコンが使い放題 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
楽楽勤怠開発1課のy_konnoです。 先日開催された JJUG CCC 2022 Fall に登壇しましたので、今回はそのレポートになります。 プロポーザル 登壇資料 登壇を振り返って Q:競合ライブラリは検討したか? Q:楽楽勤怠の事例においてミドルウェアではなくあえてJava側でやった理由は? Q:タイムアウトなどの値はどう設定しているか? まとめ プロポーザル マイクロサービスにおいては一部のサービスが障害を起こして応答が 不能 になったり、ネットワーク的にリク エス トが到達 不能 になることがあります。その際に無対策だと連鎖的に各サービスがダウンし、 アーキテクチャ ー全体が機能不全に陥ってしまいます。 対策としてはサーキットブレーカーの適用が挙げられます。サーキットブレーカーは局所的な障害が アーキテクチャ ー全体に波及しないように障害を分離する機構です。しかし、自力で実装するのはコストが高いのが難点です。 Resilience4jは Java でフォールトトレランスを実現するためのライブラリです。数多くの機能を備えますが、その中の一つにサーキットブレーカーがあります。本セッションでは以下内容について解説したいと思います。 Resilience4jの概要 Resilience4jを利用したサーキットブレーカーの実現方法の解説 Resilience4jを利用したリトライの実現方法の解説 弊社サービスである「楽楽勤怠」での適用例 fortee.jp 登壇資料 speakerdeck.com 登壇を振り返って 今回はセッション本体が動画配信で、質疑応答がリアルタイムという形式でした。動画は15分という制限時間があったので、なるべくシンプルかつ利点がストレートに伝わるように、Resilience4jの適用方法を3ステップに分けて解説してみました。 Resilience4j自体あまりメジャーではないので、テーマ被りの心配はしていませんでしたが、やっぱり被らずに済みましたw 表題としてはサーキットブレーカーをメインに据えていますが、個人的にResilience4jを使っていて最もありがたいポイントはリトライをものすごく簡単に実装できるところですね。質疑応答前に運営の方とも話をしたんですが、リトライのコードを自前で書いてつらいという話は各所で出ているのでこういったライブラリはありがたいとおっしゃってましたね。 以下は頂いた質問に対する回答をいくつか Q:競合ライブラリは検討したか? A:正直なところ、Resilience4jが真っ先に出る上にメンテされてるので、一択なところがありました。かつては Netflix がサーキットブレーカー用のライブラリを出していたんですが、それが ディスコン になっているのも影響はでかいと思います Q:楽楽勤怠の事例において ミドルウェア ではなくあえて Java 側でやった理由は? A:リトライを併せて使いたかったことと、インフラ構成を増やしたくなかったのが理由になりますね。あとは打刻アプリケーションは様々な顧客向けにリク エス トを飛ばす関係上、振り分けのロジックを管理するのが Java でやるほうが都合が良かったというのもあります Q: タイムアウト などの値はどう設定しているか? A:正直、手探り感があるところです。リトライでウェイトする秒数などはサービスで応答目標としている秒数があるのでそちらを下回るように計算しています。他方、サーキットブレーカーにおけるリングバッファのサイズなどは暫定で決めています。 まとめ マイナーなネタなので質問が全く来ないことを危惧しましたが、なんとか関心を集めることはできたようで安心しました。 リトライ目的からスタートしてResilience4jを導入する人が増えてくれると嬉しいですね。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
はじめに 本記事では、Spockの基礎について解説します。 「そもそもSpockって何?」「コードをテストするってどうやるの?」という方がSpockでテストを書く助けになれば幸いです。 目次 はじめに 目次 Spockとは テストコードを書いてみよう expectブロック whereブロック when, thenブロック given(setup)ブロック、cleanupブロック 予約メソッド(Fixture methods) Mockでテストを分割しよう おわりに 参考資料 Spockとは Spockとは、 Java コードのテストができる フレームワーク で、Groovyという言語で記述します。 Groovyを知らないという方でも、書き方が Java に近いのでほとんど同じ感覚で書くことができます。 同じく Java のコードをテストする フレームワーク に JUnit があり、よく比較されます。 どちらを使うかは好みですが、個人的にはSpockの方が直感的にわかりやすいと思っています。 JUnit とSpockを比較した記事があるので JUnit についても知りたいという方はこちらの記事もおすすめです。 tech-blog.rakus.co.jp テストコードを書いてみよう expectブロック では、さっそくSpockを用いてテストコードを書いていきましょう。 テストするコードはこちらです。 説明するまでもないですが、数字の足し算結果を返すだけのメソッドです。 テストするコード public static int add( int x, int y) { return x + y; } テスト class AddSpec extends Specification { def "add関数のテスト" () { expect : add( 2 , 3 ) == 5 } } テストするコードがシンプルなので、テストもシンプルですね。 では、上から順にみていきましょう。 Spockを使うには、Specificationを継承します。 私も中身は知らないので おまじないだと思ってとりあえず書いておいてください。また、慣例でテストクラス名は メソッド名+Spec にすることが多いです。 次に、メソッドの宣言です。 Java とは少し違い、 def 名前() {} のように書きます。 名前の部分は、例えば def addTest() {} のように書くことも、例のように""で囲んで日本語を使うこともできます。 では、メソッドの中を見てみましょう。 expect はGroovyではなく、Spock特有の構文です。 expect の他に given ( setup )、 when 、 then 、 where 、 clearnup ブロックがあり、それぞれ役割があります。(後述) expect はテストを書くブロックで、 add(2, 3) の結果が 5 だと予測(expect)することを表します。 whereブロック 次に、whereブロックを使ってみましょう。 where は、同じメソッドを色々なパターンで試すときに使用するブロックです。1つのメソッドを1パターンだけテストすることはほぼないので頻繫に使用することになると思います。 では、例を見てみましょう。テストするのは先程と同じ、足し算するメソッドです。 テスト class AddSpec extends Specification { def "add関数のテスト" () { expect : add(x, y) == result where : x | y || result 2 | 3 || 5 Integer .MAX_VALUE | 1 || 2147483648 Integer .MIN_VALUE | - 1 || - 2147483649 } } 境界値のテストを追加してみました。 このように、1行目に変数、2行目以降にパターンのように書きます。 変数( x , y , result )はメソッド内で使うことができ、例では add メソッドの引数と結果を変数にしています。 | は見た通り変数の区切りです。 | の数は1個でも2個でも同じ意味ですが、入力と出力などで区切ると見やすさがアップします。 when, thenブロック 実行結果1 add(x, y) == result | | | | | | | 1 | 2147483648 | | false | 2147483647 -2147483648 実行結果2 add(x, y) == result | | | | | | | -1 | -2147483649 | | false | -2147483648 2147483647 テストを実行したら失敗しました。 実行結果を見る限りオーバーフローが起きてしまったみたいです。 オーバーフローが起きたときはちゃんと例外を投げるように修正し、例外になるかどうかのテストも追加しましょう。 テストするコード public static int add( int x, int y) { return Math.addExact(x, y); } テスト class AddSpec extends Specification { def "add関数のテスト" () { expect : add( 2 , 3 ) == 5 } def "add関数のテスト_例外" () { when : add( Integer .MAX_VALUE, 1 ) then : thrown( ArithmeticException ) } } 例外になるかをテストするために when 、 then ブロックを使います。 when と then は必ずセットで使うブロックで、 when に実行する内容、 then に結果を書きます。例は add(Integer.MAX_VALUE, 1) を実行したときに ArithmeticException が投げられるという意味です。 given(setup)ブロック、cleanupブロック 残りのブロックについても簡単に説明しておきます。 given ( setup )はテストをするための準備、 cleanup はテストの後処理をするブロックです。 テスト class AddSpec extends Specification { def "readFileのテスト" () { given : File file = new File( "./.txt" ) BufferedReader br = new BufferedReader( new FileReader(file)) expect : def result = readFile(br) cleanup : br.close() } } ファイルを読むメソッドのテストを書いてみました。 given で引数に必要な BufferedReader を準備して cleanup で解放しています。このように、複雑なクラスを引数に取るときやメモリ解放が必要な時にこれらのブロックを使います。 ちなみに、他のブロックとは違い given ( setup )ブロックは省略することができます。 逆にそれ以外のブロックは付け忘れるとビルドエラーになるので注意しましょう。 予約メソッド(Fixture methods) Spockでは、4つの特殊なメソッドが用意されており、特定のタイミングで勝手に実行されます。 def setupSpec() {} ・・・テストクラスの最初に実行する def setup() {} ・・・各テストメソッドの最初に実行する def cleanup() {} ・・・各テストメソッドの最後に実行する def cleanupSpec() {} ・・・テストクラスの最後に実行する 以上をまとめると、下図のような実行順になります。 予約メソッドは使わなくても書けますが、使いこなせればより簡潔にテストメソッドを作成することができます。 Mockでテストを分割しよう ここまでは、1つだけで完結するメソッドのテストをしてきました。ですが、実際はメソッドの中でメソッドを呼ぶことが多く、一つのメソッドで階層深くまでテストするのは現実的ではありません。 そこで呼び出したメソッドだけテストするために、メソッド内のメソッドを「モック化」します。 モック( モックアップ )は見た目だけのハリボテのことで、主にシステムを作る前にお客様に「こんな感じのを作りますよ~」という見本で使われます。 Spockにおいてモックは、呼ばれたら実際には実行せず指定された結果を返すことを意味します。 実際にコードを見たほうがわかると思うので、さっそく例に行きましょう。 テストするコード private Calculator calc; public static List<Integer> FourArithmetic( int x, int y) { List<Integer> results = new ArrayList<>(); results.add(calc.add(x, y)); results.add(calc.sub(x, y)); results.add(calc.mul(x, y)); results.add(calc.div(x, y)); return results; } テスト def "FourArithmeticのテスト" () { def calcMock = Mock(Calculator) calcMock.add( 2 , 3 ) >> 5 calcMock.sub( 2 , 3 ) >> - 1 calcMock.mul( 2 , 3 ) >> 6 calcMock. div ( 2 , 3 ) >> 0 when : def results = FourArithmetic( 2 , 3 ) then : results == List .of( 5 , - 1 , 6 , 0 ) } 例は、 FourArithmetic メソッドのテストです。メソッド内で Calculator クラスの add 、 sub 、 mul 、 div を呼び出していますね。 次にテストを見ていきます。 まず Mock(Calculator) で Calculator クラスをモック化します。 次に、モック化すると何を実行しても null になってしまうので、下の行でメソッドを書き換えます。 例では4つのメソッドを、引数が (2, 3) で呼ばれた時だけ右側の値を返すように書き換えました。( (_, _) のようにすることで、どんな引数でも指定の戻り値を戻すよう書き換えることもできます) テストが成功すれば、 Calculator の中身を無視して、 FourArithmetic のリスト操作や引数指定だけがテストできたことになります。 また、今回は紹介しませんが Spy や Stub も、 Mock と同じようにメソッドをモック化することができます。 前述した Mock 、 Spy 、 Stub などはSpockの機能ですが、Mockitoという全く別の、モックのための フレームワーク があります。 Spockでのモック化と同じことができるだけでなく、staticメソッドをモック化したり(Mockito.mockStatic)モック化したメソッドの引数をチェックしたり(Captor)できる便利な機能があるので、興味のある方はそちらも調べてみてください。 おわりに 本記事では、Spockの使い方について解説しました。 本記事は公式ドキュメントを参考に作成しています。より正確な内容を学習したい方は 公式ドキュメント を読んでみてください。 では、最後までお読みいただきありがとうございました。 参考資料 tech-blog.rakus.co.jp qiita.com spockframework.org エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは、あるいはこんばんは。すぱ..すぱらしいサーバサイドのエンジニアの( @taclose )です☆ インフラエンジニアもサーバサイドエンジニアも ssh コマンドを叩かない日はないんじゃないでしょうかっ gitコマンドなんかでも ssh の設定が有効だったりもしますのでエンジニアにとって重要な アーティファクト と言えるでしょう。 今日は私も毎日お世話になってる ssh コマンドの話を 実践で使えるノウハウやオススメの設定をまとめて公開 していきます! ※記事の内容はインフラエンジニア1年目でも読みやすく、かつ実践を意識して記載しています。深掘りより読破重視! 目次 目次 sshとは? sshのパスワード入力を省略しよう!(公開鍵認証の話) sshのセキュリティ対策をしよう!(sshdの設定の話) sshのポート番号を変更しよう! sshdでport 20022を追加 SELinuxの設定を確認しよう! ファイアウォールを確認しよう! ssh port 22を閉じよう!!! セキュリティ設定に関するまとめ sshのコマンド入力を楽にしよう!(ssh configの話) Point1: Host __* で共通設定を書こう Point2 : gitサーバとかのHost名はHostnameと同じにしよう そしてこうなる!!! 最後に 参考文献 ssh とは? えー!そこから!?って思う方もいるでしょうが、概要だと思って聞いて下さいねっ 「 ssh とは?」なんの略かというと、 " Secure shell " を略したものです。 Wiki とかでも「暗号や認証の技術を利用して、安全にリモートコンピュータと通信するための プロトコル 」と説明されていて、安全にサーバ間通信する最も一般的な方法と言えます。 「いやいやtacloseさん、 FTP 通信もかなり主流でしょうに」という方もいらっしゃるかもしれませんが、 FTP はファイル転送 プロトコル として有名ではありますが通信が暗号化されておらず危険なため現在はファイル転送も ssh を利用したSFTPやSCPを使う事が多いですね。 ここではそんな ssh をより安全に便利に使ってもらうためのノウハウを詰め込んで説明しています。 とはいえ port変更とか必須じゃないものもあるので、やりたい所だけチョイスしてやってもらえれば良い かなと思います。 という事で、 ssh の重要性がわかった所でLet's ssh !! ssh のパスワード入力を省略しよう!(公開鍵認証の話) 公開鍵認証を使えば、安全・パスワード不要が実現出来ます。 私は以下のコマンドを Windows のWSL上で実行していますが、やりたい事は、 どこかで鍵を作成 秘密鍵 (id_ rsa )をローカルPCに配置 公開鍵(id_ rsa .pub)をサーバ側に配置 サーバ側の ssh の設定で公開鍵認証を許可 すればおしまいです。 以下は実際のコマンドです。 # username : 各自用意したサーバのuser名に置き換えてね! # hostname : 各自用意したサーバのdomain名やIPアドレスに置き換えてね! // 鍵を作成 $ ssh-keygen -t rsa -b 4096 (何も打たずにEnter2回。パスワード設定したらめんどいもんね!) // 鍵がある事を確認 $ ls -al ~/.ssh/ -rw------- 1 root root 1675 3 月 3 2022 id_rsa -rwxr-xr-x 1 root root 404 3 月 3 2022 id_rsa.pub // 公開鍵をサーバにアップロード $ ssh-copy-id username@hostname // 公開鍵がサーバに上がったかを確認してみる $ ssh username@hostname " ls -al ~/.ssh/authorized_keys " // 試しにアクセスできるか試してみる(失敗しても焦らないでネ!) $ ssh username@hostname これでパスワード入力の地獄からは解放されました!おめでとう! 少し補足説明すると、 ~/.ssh/authorized_keys というのはサーバ側の ssh の設定で標準的な公開鍵の置き場所 ssh-copy-id というコマンドはローカルにある ~/.ssh/id_rsa.pub をサーバ上の ~/.ssh/authorized_keys にアクセス権含めて良しなにアップロードしてくれるコマンド ssh username@hostname って 秘密鍵 指定してないけど!? ~/.ssh/id_rsa を使うのは標準なため省略が可能 という感じになっています。 もしかしたらログイン出来なかったなんて人いるかもしれませんが、次のセキュリティ対策の設定らへんが原因かもしれませんね。 次にいきましょう! もっとここ詳しくって方はこちらも読んでみてね! SSHは公開鍵・秘密鍵を使って楽にする - RAKUS Developers Blog | ラクス エンジニアブログ ssh のセキュリティ対策をしよう!( sshd の設定の話) 先に言います。 外部に晒されてないサーバなら正直port変更はやらなくてもまぁいいかな って思います。 飛ばしてもらってもOKです!やっておく方が良いですが無理に頑張らなくても良いかなと。 でも外部からのアクセスが許可されてるサーバであれば、いつか攻撃を受けますので設定しておく事をおすすめします。 rootアカウントでのログイン拒否 パスワードでのログイン拒否 port 22でのアクセス拒否 ここらへんは最低限やっておくべきかとは思います。 さて、本題に戻ろうっ... 既に誰かが用意してくれたサーバの場合、 ssh の設定が色々と違ってるかもしれませんので確認しながら進めましょう! 尚、 ポート開放や SELinux についてはCentOS7のやり方で書いてますので、 Ubuntu とかの人はfirewalldコマンドに置き換えて実施してくださいね ! ssh のポート番号を変更しよう! ファイアウォール とか SELinux とか確認すべき事はありますが、手順としては sshd で20022 portを許可! ファイアウォール も SELinux も 20022 portを許可! ssh で20022 port使えるか確認! ssh でport 22を閉じる!(port 20022で接続してないと接続切れちゃうよ!!) の順番なので、焦らず焦らずとりあえず許可していきましょう! sshd でport 20022を追加 sshd とは?という方のために補足すると、" sshd = ssh daemon = ssh 常駐型プログラムを指すサーバサイド側のソフトウェア" を指しています。 普段 ssh でport X番に通信飛ばして接続して何か出来るのは、この sshd が受け答えしてくれてるんですね!感謝! 先にも記載しましたが、 ssh のportが22のままだと攻撃の機会を与えてしまいますので、変更すべきです。 では以下手順に沿ってLet’s port 解放☆ // まずは設定を確認! $ grep -E " ^Port |^PermitRootLogin |^PasswordAuthentication " /etc/ssh/sshd_config // PermitRootLogin yes : rootでのログインを許可するのは危険です。可能なら no にしよう! PermitRootLogin yes // PasswordAuthentication yes : パスワード認証を許可するのは危険です。公開鍵認証の設定はしましたよね?だったらこれも no にしよう! PasswordAuthentication yes // Portの列はない: #Port 22ってコメントアウトされてる列はあるはず。Port 20022を後で追加しましょう! // 設定変更する $ vim /etc/ssh/sshd_config // 設定変更後こうなるようにしてね! $ grep -E " ^Port |^PermitRootLogin |^PasswordAuthentication " /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no Port 22 Port 20022 // 変更を反映させるため再起動っ $ systemctl restart sshd これで sshd としては20022を許可しましたが、みんなのサーバには SELinux やfirewalldが邪魔してるはずです。 また、社内ネットワークによってはローカルPC→ ファイアウォール →開発サーバとなっていて、インフラチームにport 20022の許可を求める必要もあるかもしれません。 なので、ケースバイケースによっては無理せずPort 22のままにしましょう。 SELinux の設定を確認しよう! 先輩社員に言われたかもしれない。 「 SELinux ?ヤツは四天王の中でも最弱...ヤツは無効でいいよ」 この話をすると必ずアンチが生まれ論争となり、すぱ...すぱらしいエンジニアが場を収める必要があるため、ここでは議論しない。 なので、この章では「無効な事を確認して終わり」もしくは「有効ならアクセス権を追加して終わり」という事に留める。 私も無効とする事が多いし、世の中無効が多数派かと思います。おっと、これ以上は危険か。 以下、手順です。 // SELinuxの確認。Disabled:無効, Enforcing: 有効, Permissive : 無効だが警告は出る $ getenforce Disabled # - - - - - # // 案1:SELinux無効にする方法 // SELinux無効でいいじゃん派なら以下のコマンドで無効化させておしまいっ $ setenforce 0 # - - - - - # // 案2:SELinux有効のままで、ちゃんとsshのためにport 20022 を開ける方法 // まずはsshのportが 22 で開いている事を確認。 $ semanage port --list | grep ssh ssh_port_t tcp 22 // port: 20022 を開ける $ semanage port --add --type ssh_port_t --proto tcp 20022 // sshのportが 20022 も開いた事を確認。 $ semanage port --list | grep ssh ssh_port_t tcp 20022 , 22 今は深く理解出来てないって人もとりあえずOK!!次いきましょう! ファイアウォール を確認しよう! ファイアウォール と言っても CentOS 上で動いてるfirewalldとは別にインフラチームが管理してる機器もあります。そっちはインフラチームに任せましょう。 ここではfirewalldについて説明します。 // まずはファイアウォールがinactiveかactiveか確認。activeじゃないなら読み飛ばしてOK $ systemctl status firewalld Active: inactive ( dead ) // ←firewalldが動いていない例です # - - - - - # // 案1:firewalldを無効化にして終わらせる方法 // 常駐化 OFF (サーバ再起動しても自動起動しなくなります) $ systemctl disable firewalld.service // ファイアウォールの停止コマンド $ systemctl stop firewalld.service # - - - - - # // 案2:firewalldでちゃんとport 20022 をssh用に開ける方法 // firewalldにport 20022 のssh設定を追加する $ cp /usr/lib/firewalld/services/ssh.xml /etc/firewalld/services/ssh-20022.xml # firewallの設定をコピー $ vim /etc/firewalld/services/ssh-20022.xml # 書き換える port="22"をport="20022"にする $ diff /usr/lib/firewalld/services/ssh.xml /etc/firewalld/services/ssh-20022.xml // 差分はこうなるはず 5c5 < < port protocol = " tcp " port = " 22 " / > --- > < port protocol = " tcp " port = " 20022 " / > // 追加したport設定を読み込ませる $ firewall-cmd --add-service=ssh-20022 --permanent // 反映するために再起動 $ firewall-cmd --reload ssh port 22を閉じよう!!! 当然ながらここまでの説明で飛ばした所はここも関係ない所飛ばしつつ設定してくださいね! 以下、手順です。 // まずはローカルから 20022 で接続できるかを確認しましょう。 $ ssh username@hostname -p 20022 // sshdからPort 22 の設定を消す $ vim /etc/ssh/sshd_config Port 22 // ←この記述を消しましょう // sshd再起動! $ systemctl restart sshd.service // SELinuxからPort 22 のアクセスを拒否させる $ semanage port --delete --type ssh_port_t --proto tcp 22 // SELinuxでPort 22 が禁止されて、 20022 だけになった事を確認 $ semanage port --list | grep ssh ssh_port_t tcp 20022 // firewalldからPort 22 のアクセスを拒否させる $ firewall-cmd --permanent --remove-service=ssh // firewalldの再起動 $ systemctl restart firewalld.service // firewalldからsshのサービス設定が消えた事を確認 $ firewall-cmd --list-all | grep ssh services: dhcpv6-client ssh-20022 // ← sshというのがなくなってる。 // 最後に念のためPort 22 でログイン試みてみてもいいかもね!絶対無理だけどね!! $ ssh username@hostname -p 20022 セキュリティ設定に関するまとめ うん、すごく面倒だったけど頑張りましたね!えらい!エンジニア歴浅い人は一度 Ubuntu のサーバとか1から設定してみると良いですよ! まずは firewall で ssh 以外のポート全部閉じて、必要な所だけ開いていって、あ~なんか無駄なものがないって気持ちいいってなればいいなと思います! ssh のコマンド入力を楽にしよう!( ssh configの話) これ系はconfigファイルの書き方次第でプログラムでいう継承関係とかも実現出来るので、こだわって色々アレンジしてみてほしいです。 サンプルで私のものを一部公開しますね! ※IPとかUser名はブログ用に修正してます。サンプルです! $ cat .ssh/config Host __* ServerAliveInterval 60 User taclose Host __taclose.develop Hostname 192 . 168 . 100 . 32 IdentityFile ~/.ssh/id_rsa Port 20022 Host __taclose.staging Hostname 192 . 168 . 100 . 32 IdentityFile ~/.ssh/id_rsa Port 20022 Host git. local .server Hostname git. local .server IdentityFile ~/.ssh/id_rsa.git Host __log_server Hostname office.log-server.jp User maeda この設定ファイルの置き場所は .ssh/config ここ固定です。各自ないなら作りましょう! 指定できるパラメータ次第では ssh のProxy設定であったり、Portだったり色々指定できます。そこらへんは詳しくまとめてくれてある記事があるので、こちらを参照してください。 ~/.ssh/configを使ってSSH接続を楽にする - RAKUS Developers Blog | ラクス エンジニアブログ ここでは設定のノウハウだけ以下に書いておきますね。 Point1: Host __* で共通設定を書こう $ yum install -y bash-completion サラッと大事な事上に書いてますが bash -completionを入れておけば入力補完が効いて便利です! そして、このHost指定に __と入れる事で ssh コマンドを打つ際に余計な入力候補が出なくなります。 やっておかないと、/etc/hosts的な場所に定義してあるものとか余計なものまで候補に出て探しにくいのです。 そして、 アスタリスク * を使う事で 正規表現 指定が可能なので、「__から始まるHost指定全部にこの設定ね!」と出来ます。 なので、「共通設定は接続切れないようにしたいのと、だいたいユーザ名はtacloseだな」とかを定義しておけばOK!! Point2 : gitサーバとかのHost名はHostnameと同じにしよう これは git clone とかする際にも公開鍵認証の設定が有効なためです。そしてgitのサーバには ssh コマンドで入らないので__は不要かなと... そしてこうなる!!! ssh コマンドの入力が非常に楽!パスワード不要! ん~ ssh の入力補完も無駄な候補がないし、指定も楽ですね! ssh のパスワード不要も素敵! 最後に セキュリティ絡みが長くなりましたが、重要な事なので皆さんも一度設定してみてサーバの知識をより深めてもらえればいいんじゃないかなって思います。 AlmaLinuxへの移行とか検討されてる方にしても、 CentOS と同様に RHEL のクローンOSです。 つまり、各種設定は CentOS の知識がそのまま使えます。 AlmaLinuxのサーバ構築する際には是非、 firewall で全部のポート閉じて必要なポートだけ開けてあげてください。 きっと秘密基地にのぞき穴だけ開いてるそんな居心地の良いサーバになります。 やってみると楽しいですよ! 参考文献 CentOS 7 で sshd のポート番号を変更する方法 | ~/.ssh/configを使ってSSH接続を楽にする - RAKUS Developers Blog | ラクス エンジニアブログ SSHは公開鍵・秘密鍵を使って楽にする - RAKUS Developers Blog | ラクス エンジニアブログ エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは、 ラク ス入社1年目の koki _matsuraです。 本日は前回記事の「【Recoil】Reactの状態管理ライブラリ基礎学習 ~第三部~」で作成したToDoアプリの Recoil部分をリファクタする とともに パフォーマンスを上げるためにどうするか についてご紹介させていただきます。 もし、前回の記事を読んでいない方は下記のリンクから読んでいただけると嬉しいです。 tech-blog.rakus.co.jp アジェンダ は以下の通りになっています。 Recoilを用いたToDoアプリの問題点 複数人でRecoilを使うときに意図しない状態変更が行われる可能性 無駄な部分まで走る再レンダリング 解決方法 「複数人でRecoilを使うときに意図しない状態変更が行われる可能性」の解決方法 「無駄な部分まで走る再レンダリング」の解決方法 コード修正 TodoStateの修正 Atomの正規化 変更系カスタムフックの作成 取得系カスタムフックの作成 ContainerとPresenterの修正 入力部と出力部の分割 ToDoを表示するコンポーネントの作成 Recoilと接続 パフォーマンスのチェック 終わりに Recoilを用いたToDoアプリの問題点 前回作成したToDoアプリはRecoilの基礎を抑えるために重要な部分を切り落としたものになっていました。 仕様通りではありましたが、前回のToDoアプリには大きく2つの問題点があると思いました。  ・ 複数人でRecoilを使うときに意図しない状態変更が行われる可能性  ・ 無駄な部分まで走る再 レンダリング それぞれについて解説します。 複数人でRecoilを使うときに意図しない状態変更が行われる可能性 Recoilの状態管理の仕組みは以下のようになっています。 RecoilはReduxのように状態の加工から管理までをするのではなく、状態の管理のみを担当し、Hooks API を使って コンポーネント 側から値を更新できるのが特徴だと思います。 とてもシンプルな仕組みで使いやすく強力な状態管理ライブラリですが、このシンプルさが規模が大きくなってきたときに弱みになる可能性があります。 今回のようなToDoアプリの場合は規模感としてはかなり小さいですし、使っているのも自分一人だけでした。これが大きなアプリになって、一人だけでなく、複数人で開発していく場合、Recoilは下記のような状態になると考えられます。 おそらく、実際に複数人で使うとなると図よりももっと複雑になると思います。 色々な コンポーネント から利用され、状態を更新されるため、Aさんが担当した コンポーネント で更新した状態はCさんが担当した コンポーネント では意図した状態でない可能性があります。 これは後々、大きな問題点になると思われます。 無駄な部分まで走る再 レンダリング 前回、作成したToDoアプリは一つのContainer、Presenterで構成されています。 なので、フォームに文字を入力したり、追加、削除、完了・未完了の切り替えボタンを押すたびに全てに再 レンダリング がかかってしまいます。 色のついた枠は再 レンダリング されている コンポーネント です。何か操作をするたびに全てが レンダリング されていることがわかると思います。 今の時点では表示するToDoリストの数が少ないためそれほど重たい処理に感じることはありませんが、リストが数百件単位になると、かなり重くなるだろうと思われます。 変化していない部分を再 レンダリング するのは無駄ですし、パフォーマンス的に問題があると言えます。 解決方法 「複数人でRecoilを使うときに意図しない状態変更が行われる可能性」の解決方法 この問題は他の コンポーネント が Atom (状態)を好きに変更できることにより発生するものです。なので解決策としては単純ですが、 Atom に対して行える操作を指定すれば良いです。 具体的にはカスタムフックを使用して、 Atom を直接変更できないようにします。 変更も取得もカスタムフック越しに行うようにします。これで意図しない変更が行われる可能性は大きく下がると思います。 また、この問題点とはあまり関係ないのですが、一つの Atom に持たせる状態の大きさにも気をつけます。深いネストを持った状態を一つの Atom に持たせるのは変更がとても複雑になってしまいます。できる限り正規化を意識した方が良いです。 現状のToDoアプリは一つの Atom にToDoのオブジェクトを配列で持たせているので、これを Atom 一つに対してToDo一つのように変更します。そうすれば、操作用カスタムフック内のコードがシンプルになります。 Reduxのスタイルガイドですが状態の正規化に関しては ここ に書かれています。 「無駄な部分まで走る再 レンダリング 」の解決方法 現在は入力部、出力部を一つの コンポーネント で記述しているため、どこかで更新が起きると全てが再 レンダリング されてしまいます。 まず、入力部と出力部を分ければ、お互いの影響で再 レンダリング されることは無くなります。 ですが、最もパフォーマンス面に影響するのは出力部にあるToDoリストの レンダリング だと思います。 例えば、新しいToDoを追加したときに既存のToDoリスト内のToDoは何も変わらないのに全てを再 レンダリング するのはあまりにも無駄です。削除や、完了・未完了の切り替えに関しても同じことが言えます。 また、ToDoを表示する コンポーネント を出力部の子 コンポーネント として新規作成し、分割をしても再 レンダリング を防ぐことはできません。なぜなら、 コンポーネント は以下の条件に当てはまったときに再 レンダリング されるためです。  Stateが更新されたか  Propsが更新されたか  親 コンポーネント が再 レンダリング されたか 出力部のPropsが更新されて、再 レンダリング されてしまうと子 コンポーネント へのPropsに変更がなくても強制的に子 コンポーネント は再 レンダリング されます。 ですが、React.memoを使うことで、Propsが変更された場合にだけ再 レンダリング するようにできます。 React.memoとは、下図のように送られてきたPropsが前回のものと比べて同じかチェックして再 レンダリング するべきか判断してくれるようにするものです。 メモ化することで、追加などの操作をしても関係のないToDoが再 レンダリング されることがなくなり、かなりパフォーマンスが上がることが期待できます。 では、これから実際に前回のToDoアプリのコードを編集し、問題点を解決していきます。 コード修正 TodoStateの修正 まずは、状態管理部分の修正をします。 コンポーネント 側では直接、 Atom を変更できないようにし、カスタムフック越しに変更や取得ができるようにします。また、 Atom 一つにToDoリストを管理させるのではなく、 Atom 一つに対し、一つのToDoを管理させるようにします。 手順は以下のようになります。 1. Atom の正規化 2.変更系カスタムフックの作成 3.取得系カスタムフックの作成 Atom の正規化 現時点のToDoリストの Atom は以下のようになっています。 export const todosState = atom < Todo [] >( { key: AtomKeys.TODOS_STATE , default : [ { id: 1 , title: "テスト1" , content: "テスト1の内容" , isCompleted: false } , { id: 2 , title: "テスト2" , content: "テスト2の内容" , isCompleted: false } ] , } ); これを正規化していきます。ですが、ToDo一つに対し、 Atom 一つで管理するために Atom をその数だけ作るのは非効率です。「atomFamily」を使います。 atomFamilyは同じ型の情報をもつ atom の集合体のようなものです。atomFamilyにはパラメータを設定でき、パラメータにToDoのIDを指定することで特定の情報を抜き出すことが可能になります。 実際にtodosStateを次のように変更しましょう。第一引数には atom の型を、第二引数には特定の atom を指定するための値が入ります。IDで指定するのでnumber型にします。 また、 コンポーネント 側から直接操作できないように「export」は消しておきます。 // todoState.ts const todoState = atomFamily < Todo| null , number >( { key: AtomKeys.TODOS_STATE , default : null } ) ToDoを複数管理することができましたが、IDの管理をしていないため、ある時点で最大のIDはいくつなのか、特定のIDは存在しているのかがわからなくなってしまいます。よって、次の Atom を加え、ToDoのIDを管理します。 //todoState.ts const todoIdState = atom < number [] >( { key: AtomKeys.TODOID_STATE , default : [] } ) keyは「recoilKeys.ts」に新たに作成します。 //recoilKeys.ts export const AtomKeys = { "TODOS_STATE" : "todosState" , "TODOID_STATE" : "todoIdState" , } 正規化が完了しました。これで、IDを指定し、atomFamilyから特定のToDoを取り出せるようになりました。 変更系カスタムフックの作成 Atom を変更するためのカスタムフックを作成していきます。 変更系のカスタムフックを作成する目的は Atom に対して行える操作を特定の範囲内に抑えるためです。ToDoアプリならば、追加と削除、完了・未完了の切り替えのみの操作に抑えるべきです。 「TodoContainer. tsx 」内に記述した変更処理とRecoilに関わるものを削除し、「todoState.ts」で下記のようにカスタムフックにします。 // todoState.ts let id = 1 ; const getId = () => { return id ++; } export const useTodoAction = () => { /** 追加処理 */ const addTodo = useRecoilCallback (( { set } ) => ( title: string , content: string ) => { const newTodo: Todo = { id : getId (), title : title , content : content , isCompleted: false } set( todoIdState , prev => [ ...prev , newTodo.id ] ); set( todosState ( newTodo.id ), newTodo ); } , [] ) /** 削除処理 */ const removeTodo = useRecoilCallback (( { set, reset } ) => ( targetId : number ) => { set( todoIdState , prev => prev.filter ( id => id !== targetId )); reset ( todosState ( targetId )) } , [] ) /** 完了の切り替え */ const toggleComplete = useRecoilCallback (( { set } ) => ( targetId: number ) => { set( todosState ( targetId ), todo => { if ( ! todo ) return null return { ...todo , isCompleted: ! todo.isCompleted } } ) } , [] ) return { addTodo , removeTodo , toggleComplete , } } 「useTodoAction」がカスタムフックです。ポイントなのは処理をする関数を囲む形で使う「useRecoilCallback」です。 useRecoilCallbackを使わないでこのカスタムフックの関数をPropsに渡すと コンポーネント で更新が起きるたびに新しい関数を生み出してしまい、余計な再 レンダリング を起こしてしまいます。 この再 レンダリング を抑えるのがuseRecoilCallbackです。関数をメモ化してくれるものと思えば簡単です。 引数には、「snapShot」「gotoSnapshot」「set」「reset」「refresh」「transact_UNSTABLE」が入れられます。 大体の場合はsetやresetで十分かと思われます。 「set」は特定の Atom やSelectorに値を設定するのに使います。 「reset」は特定の Atom やSelectorの値をdefault値にします。削除処理で用いていますが、この場合は対象の Atom の値がnullになるということです。 atomFamilyを用いたことにより対象以外のToDoを読み込むことなく、対象のToDoのみを扱うことが可能になりました。その分、処理も簡単に書けることがわかります。 取得系カスタムフックの作成 次は特定のToDoとIDの全件を取得するカスタムフックの作成をします。 「todoState.ts」に次のコードを加えます。 const getTodo = selectorFamily < Todo| null , number >( { key: SelectorKeys.GET_TODO , get : todoId => ( { get } ) => get( todosState ( todoId )) } ) export const useGetTodoAction = () => { const todoIds = useRecoilValue ( todoIdState ); const useGetTodo = ( todoId: number ) => useRecoilValue ( getTodo ( todoId )); return { todoIds , useGetTodo } } 「useGetTodoAction」がカスタムフックです。中身は単純で、useRecoilValueでtodoIdStateとgetTodoの値を取得しています。 getTodoは、引数にIDを入れることでそのIDと合致するToDoを返すSelectorFamilyです。 これで、「todoState.ts」の修正が完了しました。 コンポーネント 側の修正をしていきましょう。 ContainerとPresenterの修正 コンポーネント のパフォーマンス部分の修正をしますが、その前にもう一度、 コンポーネント 側の問題点と解決方法を振り返ります。 現時点では入力部、出力部を一つの コンポーネント で記述しており、更新が起きると全てが再 レンダリング されてしまいます。なので、入力部と出力部を分割します。 次に レンダリング コストの高いToDoリスト部分が問題になります。変更が起きていないToDoまで再 レンダリング されるのは無駄なので、出力部にToDoを出力する子 コンポーネント を作成し、その コンポーネント をメモ化をすることでPropsに変更がない限りは再 レンダリング されないようにしましょう。 手順は以下のようになります。 入力部と出力部の分割 ToDoを表示する コンポーネント の作成 Recoilと接続 入力部と出力部の分割 まずは入力部と出力部の分割です。 入力部のContainer・Presenterと出力部のContainer・Presenterをそれぞれ作成します。下記のような構成にしましょう。 変更点は以下のようになってます。 TodoPresenter. tsx の削除 TodoContainer. tsx の削除 container ディレクト リの新規作成 TodosContainer. tsx を新規作成 TodosInputContainer. tsx の新規作成 TodosOutputContainer. tsx の新規作成 presenter ディレクト リの新規作成 TodosInputPresenter. tsx の新規作成 TodosOutputPresenter. tsx の新規作成 ※「TodoPresenter. tsx 」を削除する際に、中身をどこかに保管しておくといいかもしれないです。 「TodosContainer. tsx 」は「TodosInputContainer. tsx 」「TodosOutputContainer. tsx 」を返すようにします。 //TodosContainer.tsx export const TodosContainer = () => { return( <> < TodosInputContainer / > < TodosOutputContainer / > < / > ) } 「TodosInputContainer. tsx 」は一旦、「TodosInputPresenter. tsx 」を返すだけにします。「TodosOutputContainer. tsx 」も同様です。 //TodosInputContainer.tsx export const TodosInputContainer = () => { return < TodosInputPresenter / > } //TodosOutputContainer.tsx export const TodosOutputContainer = () => { return < TodosOutputPresenter / > } 「TodosInputPresenter. tsx 」には、入力部のコードを書きます。addTodo関数はエラーが出てしまいますが、そのままにしておきます。 //TodosInputPresenter.tsx export const TodosInputPresenter = () => { const [ title , setTitle ] = useState ( "" ); const [ content , setContent ] = useState ( "" ); const sendTodo = () => { addTodo ( title , content ) setTitle ( "" ) setContent ( "" ) } return ( < form > < label > タイトル: < input type= "text" value = { title } onChange = { e => setTitle ( e.target.value ) } / > < /label > < label > 内容: < input type= "text" value = { content } onChange = { e => setContent ( e.target.value ) } / > < /label > < button type= "button" onClick = { () => sendTodo () } > 送信 < /button > < /form > ) } 「TodosOutputPresenter. tsx 」には、出力部のコードを書きます。todosがないのでエラーが出ますが、これもそのままにしておきます。 //TodosOutputPresenter.tsx export const TodosOutputPresenter = () => { return ( <> < div >------------------------ - < /div > < h1 > Todoリスト < /h1 > { todos.map (( todo : Todo )=> { return ( < React.Fragment key = { todo.id } > < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' onClick = { () => toggleComplete ( todo.id ) } > { todo.isCompleted ? "戻す" : "完了" } < /button > < button type= 'button' onClick = { () => removeTodo ( todo.id ) } > 削除 < /button > < /React.Fragment > ) } ) } < / > ) } 所々、エラーは出ていますが、入力部と出力部を分割することができました。 ToDoを表示する コンポーネント の作成 出力部の子 コンポーネント として、ToDoを表示する コンポーネント を作成します。 container ディレクト リ下に「TodoOutputContainer. tsx 」を、presenter ディレクト リの下に「TodoOutputContainer. tsx 」を新規作成します。 これを「TodosOutputPresenter. tsx 」のToDoリストを表示している部分に入れます。現時点では何もPropsで与えていませんが、一旦これで置いておきます。 次のように書き換えましょう。 //TodosOutputPresenter export const TodosOutputPresenter = () => { return ( <> < div >------------------------ - < /div > < h1 > Todoリスト < /h1 > { todos.map (( todo : Todo )=> < TodoOutputContainer / > ) } < / > ) } 「TodoOutputContainer. tsx 」は「TodoOutputPresenter. tsx 」を返すのみにしておきます。 //TodoOutputContainer.tsx export const TodoOutputContainer = () => { return < TodoOutputPresenter / > } そして、「TodoOutputPresenter. tsx 」でToDoを表示するようにします。引数を入れるところから最後までを「memo」で囲むことでメモ化することができます。 todoや、その他の操作でエラーが出ていますが一旦、そのままで問題ありません。 //TodoOutputPresenter export const TodoOutputPresenter = memo (() => { return ( <> < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' onClick = { () => toggleComplete ( todo.id ) } > { todo.isCompleted ? "戻す" : "完了" } < /button > < button type= 'button' onClick = { () => removeTodo ( todo.id ) } > 削除 < /button > < / > ) } ); Recoilと接続 コンポーネント の分割が終わったので、あとは「todoState.ts」で作成したカスタムフックを コンポーネント 側で使うだけです。 まずは入力部から繋いでいきます。「TodosInputContainer. tsx 」では、Todoを追加するaddTodoを使います。 //TodosInputContainer.tsx export const TodosInputContainer = () => { const { addTodo } = useTodoAction (); return < TodosInputPresenter addTodo = { addTodo } / > } 「TodosInputPresenter. tsx 」にPropsでaddTodoを渡します。 //TodosInputPresenter.tsx type TodosInputPresenterProps = { addTodo : ( title: string , content: string ) => void } export const TodosInputPresenter : React.FC < TodosInputPresenterProps > = ( { addTodo } ) => { const [ title , setTitle ] = useState ( "" ); const [ content , setContent ] = useState ( "" ); const sendTodo = () => { addTodo ( title , content ); setTitle ( "" ) setContent ( "" ) } /* 省略 */ これで入力部は大丈夫です。 出力部の「TodosOutputContainer. tsx 」にはIDのリストを、ToDoを表示する「TodoOutputContainer. tsx 」にはIDからToDoを取り出す操作、削除の操作、完了・未完了の切り替えの操作をカスタムフックから取得します。 「TodosOutputContainer. tsx 」を編集します。 //TodosOutputContainer.tsx export const TodosOutputContainer = () => { const { todoIds } = useGetTodoAction (); return < TodosOutputPresenter todoIds = { todoIds } / > } 「TodosOutputPresenter. tsx 」を編集します。この コンポーネント はメモ化します。todoIdsに更新がない限りは再 レンダリング がされません。つまり、完了・未完了の切り替えの時は レンダリング がされません。 「TodoOutputContainer. tsx 」でIDが必要になるので、Propsで渡します。 //TodosOutputPresenter.tsx type TodosOutputPresenterProps = { todoIds : number [] } export const TodosOutputPresenter : React.FC < TodosOutputPresenterProps > = memo (( { todoIds } ) => { return ( <> < div >------------------------ - < /div > < h1 > Todoリスト < /h1 > { todoIds.map ( todoId => < TodoOutputContainer todoId = { todoId } / > ) } < / > ) } ) 「TodoOutputContainer. tsx 」では、親からきたIDを受け取ります。 「TodoOutputPresenter. tsx 」にはIDからToDoを取り出す関数ではなく、取り出した後のToDoと削除、切り替えの関数を渡します。 ToDoを取り出す関数を渡さない理由としては、useGetTodo関数はメモ化されていないためです。この関数をPropsで渡してしまうと、何か更新が走るたびにReactは新しい関数としてuseGetTodoを受け取ってしまい、全てのToDoが再 レンダリング されてしまいます。 //TodoOutputContainer.tsx type TodoOutputContainerProps = { todoId : number } export const TodoOutputContainer : React.FC < TodoOutputContainerProps > = ( { todoId } ) => { const { useGetTodo } = useGetTodoAction (); const { removeTodo , toggleComplete } = useTodoAction (); const todo = useGetTodo ( todoId ); const args = { todo , removeTodo , toggleComplete } return < TodoOutputPresenter { ...args } / > } 最後に「TodoOutputPresenter. tsx 」を編集します。 //TodoOutputPresenter.tsx type TodoOutputPresenterProps = { todo: Todo | null , removeTodo: ( id: number ) => void , toggleComplete: ( id: number ) => void } export const TodoOutputPresenter : React.FC < TodoOutputPresenterProps >= memo (( { todo , removeTodo , toggleComplete } ) => { if( ! todo ) return <>< / >; return ( <> < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' onClick = { () => toggleComplete ( todo.id ) } > { todo.isCompleted ? "戻す" : "完了" } < /button > < button type= 'button' onClick = { () => removeTodo ( todo.id ) } > 削除 < /button > < / > ) } ); パフォーマンスのチェック メモやカスタムフックを用いて、多くの編集をしてきました。実際にどのくらいパフォーマンスが変わるかチェックしてみます。 注目する点は 入力部と出力部がお互いの影響で再 レンダリング されていないか 変更のないToDoが無駄に再 レンダリング されていないか まずは入力部と出力部がお互いの影響で再 レンダリング されていないかをチェックします。入力部・出力部・ToDoの コンポーネント 部分にconsole.logを仕込みました。 入力部は文字を打ち込むたびに変更が起きてしまうので再 レンダリング が頻繁に起こってしまっています。ですが、出力部には全く影響が出ていないことが確認できます。しっかりと コンポーネント を分割したおかげです。 また、今回はしませんでしたが、入力部もさらにタイトル部分、コンテンツ部分、ボタンで コンポーネント を分けることでさらに再 レンダリング の範囲を狭めることが可能です。 次は、変更のないToDoが無駄に再 レンダリング されていないかをチェックします。入力部がかなりログを吐いてしまうので入力部のログは消しています。 ToDo追加時に状態に更新があるのは、ToDoのIDリストと、追加されたToDoのみなので、出力部と追加されたToDoのみが レンダリング されています。一見全てが再 レンダリング されているように見えますが、もしToDoが レンダリング されていれば、「ID:○の レンダリング 」とログを出すはずなので、しっかりとメモ化されていることがわかります。 削除時は出力部のみ再 レンダリング されています。変更のないToDoは追加と同様にメモ化されたものが使われています。 更新時はToDoのIDリストには変更がないため対象のToDoのみが再 レンダリング されていることがわかります。 コンポーネント のメモ化と、関数のメモ化により、変更のないToDoには全く影響がないようになっています。しっかりとパフォーマンスが上がったかと思われます。 終わりに 今回の記事では、前回作成したRecoilを用いたToDoアプリの問題点を例にRecoilの安全な運用方法と、無駄な再 レンダリング の削減方法について書かせていただきました。 Recoilにはまだまだ他にも機能があるのでご興味があれば公式サイトを調べてみてください! recoiljs.org また、良ければRecoil以外にもRedux、Redux-Toolkitについて書いた記事もあるので読んでいただけると嬉しいです。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
勤怠サービスの開発チームに所属しているkarabishです。 テストに関するある課題を解決するために API テストの自動化ツールを調査しました。まだチーム内に展開していないのですが、調査結果のうちツールの選定に関する部分を備忘録として残しておこうと思います。 なぜAPIテストを自動化するのか ツールの選定方針 調査したツールたち 調査方法 調査結果 Tavern テストシナリオ テスト実行 scenarigo テストシナリオ テスト実行 runn テストシナリオ テスト実行 karate テストシナリオ テスト実行 stepci テストシナリオ テスト実行 調査しなかったツールたち まとめ なぜ API テストを自動化するのか 36協定の計算などの負荷が重たい処理はpub/sub アーキテクチャ を利用して非同期で処理していました。ただ、publish側とsubscribe側それぞれの ユニットテスト は存在していたのですが、全体に関するテストは自動化されていませんでした。そのため、pub/sub全体に関するテスト観点を API テストで自動化しようと目論んだのが発端となります。 ツールの選定方針 選定方針を挙げるとすればこの3つになるのかと思います。 CIとの親和性が高いこと dockerで扱いやすいこと yaml などのテキストに定義するだけでテストができること なぜこの3点なのかというと、 API テスト自体はCI上で実行する予定のため「CIとの親和性」が重要で、CIは Gitlab CI ( executor はdockerを利用)のためdockerで実行できる必要があり、dockerで実行するとなるとコードを書かずに yaml などで定義できればありがたい、という背景になります。 調査したツールたち 調査したツールたちは以下の5つです。調査はしませんでしたが候補としてあがったツールたちは参考までに 調査しなかったツールたち にまとめてあります。 ツール URL ライセンス 実装する言語 対応している プロトコル dockerイメージ Tavern 公式サイト , Github MIT license yaml , python http, MQTT ? scenarigo Github Apache-2.0 license yaml , golang http, gRPC ? runn Github MIT license yaml , golang http, gRPC, DB, Chrome DevTools Protocol, SSH /Local command ghcr karate 公式サイト , Github MIT license Gherkin http, GraphQL ? stepci 公式サイト , Github MPL-2.0 license yaml REST, GraphQL, gRPC, tRPC, SOAP ghcr 調査方法 OpenAPIのpetstore.yaml を利用したモック環境を用意する GET /pet/1 リク エス トに対して成功した場合(200 OKを期待値とする)を実施する % docker run --name openapi -d -p 4010:4010 stoplight/prism:3 mock -d -h 0.0.0.0 https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/3_0/petstore.yaml 9b6c556320adfacd9b3a497af35df5774b64abd406f022478254e1cc7ec42600 # cURLで実行した場合のレスポンス % curl -sS -H 'api_key: special-key' -H "Accept: application/json" http://localhost:8080/pet/1 | jq . { "name": "officia magna", "photoUrls": [ "Duis ex incididunt", "sit" ], "id": 1824362991613808600, "category": { "id": -6773680898015056000, "name": "zq8QbcVd.U6hh9W0Pl01Dpq" }, "tags": [ { "id": 623842466761203700, "name": "dolor est occaecat ea adipisicing" } ], "status": "available" } 調査結果 Tavern テストシナリオ % cat api-test.tavern.yaml test_name: sample stages: - name: GET /pet/1 request: url: http://ローカルのIPアドレス:8080/pet/1 method: GET headers: accept: application/json api_key: special-key response: status_code: 200 テスト実行 公式のdockerイメージが見つからなかったため、 chatworkさん提供のイメージ を利用させていただいた % docker run -it --rm -v ${PWD}:/tavern chatwork/tavern:1.7.0 /tavern/api-test.tavern.yaml . ------------------------------------------------------------------------------ Ran 1 tests in 0.25s OK scenarigo テストシナリオ # cat scenarios/api-test.yaml title: sample steps: - title: GET /pet/1 protocol: http request: method: GET url: 'http://ローカルのIPアドレス:8080/pet/1' header: accept: application/json api_key: special-key expect: code: 200 テスト実行 golangのdockerイメージ を実行し、scenarigoを go install でインストールする % docker run -it --rm -v ${PWD}:/tests golang:1.19.3-bullseye bash # go install github.com/zoncoen/scenarigo/cmd/scenarigo@v0.12.8 scenarigoの設定ファイルを作成する。 scenarigo config init でテンプレートは作成可能 # cat scenarigo.yaml schemaVersion: config/v1 scenarios: # Specify test scenario files and directories. # ↓このディレクトリにシナリオを配置する - scenarios pluginDirectory: ./gen # Specify the root directory of plugins. # plugins: # Specify configurations to build plugins. # plugin.so: # Map keys specify plugin output file path from the root directory of plugins. # src: ./path/to/plugin # Specify the source file, directory, or "go gettable" module path of the plugin. output: verbose: false # Enable verbose output. # colored: false # Enable colored output with ANSI color escape codes. It is enabled by default but disabled when a NO_COLOR environment variable is set (regardless of its value). # report: # json: # filename: ./report.json # Specify a filename for test report output in JSON. # junit: # filename: ./junit.xml # Specify a filename for test report output in JUnit XML format. scenarigo run でテストを実行する # cd /tests/ # scenarigo run ok scenarios/api-test.yaml 0.036s scenarigo. yaml の output.verbose を true にすることでリク エス トとレスポンスが標準出力される # scenarigo run === RUN scenarios/api-test.yaml === RUN scenarios/api-test.yaml/sample === PAUSE scenarios/api-test.yaml/sample === CONT scenarios/api-test.yaml/sample === RUN scenarios/api-test.yaml/sample/GET_/pet/1 --- PASS: scenarios/api-test.yaml (0.03s) --- PASS: scenarios/api-test.yaml/sample (0.03s) --- PASS: scenarios/api-test.yaml/sample/GET_/pet/1 (0.03s) [0] send request request: method: GET url: http://ローカルのIPアドレス:8080/pet/1 header: Accept: - application/json Api_key: - special-key User-Agent: - scenarigo/v0.12.8 response: header: Access-Control-Allow-Credentials: - "true" Access-Control-Allow-Headers: - "*" Access-Control-Allow-Origin: - "*" Access-Control-Expose-Headers: - "*" Connection: - keep-alive Content-Length: - "222" Content-Type: - application/json Date: - **** body: category: id: "1575230569806000000" name: 9O7c1pJOPktof id: "8291010298486120000" name: consequat nulla sint photoUrls: - consequ status: pending tags: - id: "1005964459763441700" name: dolor Lorem sunt elapsed time: 0.027939 sec PASS ok scenarios/api-test.yaml 0.033s runn テストシナリオ % cat api-test.yaml desc: sample runners: req: http://ローカルのIPアドレス:8080 steps: getPet: req: /pet/1: get: headers: accept: "application/json" api_key: "special-key" test: steps.getPet.res.status == 200 テスト実行 用意されているdockerイメージ を利用することで実行することができる % docker run -it --rm --name runn -v $PWD:/books ghcr.io/k1low/runn:v0.52.3-slim run /books/api-test.yaml sample ... ok 1 scenario, 0 skipped, 0 failures --debug オプションを指定することでリク エス トとレスポンスの内容が標準出力される % docker run -it --rm --name runn -v $PWD:/books ghcr.io/k1low/runn:v0.52.3-slim run --debug /books/api-test.yaml Run 'req' on 'sample'.steps.getPet -----START HTTP REQUEST----- GET /pet/1 HTTP/1.1 Host: ローカルのIPアドレス:8080 Accept: application/json Api_key: special-key -----END HTTP REQUEST----- -----START HTTP RESPONSE----- HTTP/1.1 200 OK Content-Length: 452 Access-Control-Allow-Credentials: true Access-Control-Allow-Headers: * Access-Control-Allow-Origin: * Access-Control-Expose-Headers: * Connection: keep-alive Content-Type: application/json Date: **** {"name":"adipisicing eiusmod Excepteur nostrud","photoUrls":["ipsum dolor"],"id":4648156526148719000,"category":{"id":-1107853279573229600,"name":"8PbcHZGjikgwWWr"},"tags":[{"id":-3453614735764951000,"name":"dolore"},{"id":-6507718611499348000,"name":"ex Excepteur laboris mollit occaecat"},{"id":2671625534111551500,"name":"mollit"},{"id":-9126589410744205000,"name":"id Ut Lorem aliqua"},{"id":2174855446376759300,"name":"sed enim"}],"status":"sold"} -----END HTTP RESPONSE----- Run 'test' on 'sample'.steps.getPet sample ... ok 1 scenario, 0 skipped, 0 failures karate テストシナリオ % cat api-test.feature Feature: sample Background: * def host = 'localhost:8080' * def httpHeaders = { accept: 'application/json', api_key: 'special-key' } Scenario: GET /pet/{id} Given url 'http://' + host + '/pet/1' And configure headers = httpHeaders When method get Then status 200 テスト実行 Github からjarファイルをダウンロードしておく java -jar ${ダウンロードしたjarファイル} {テストシナリオ} で実行することができる % java -jar karate-1.3.0.jar api-test.feature 00:00:00.000 [main] INFO com.intuit.karate - Karate version: 1.3.0 00:00:00.000 [main] INFO com.intuit.karate.Suite - backed up existing 'target/karate-reports' dir to: target/karate-reports_1669956006172 00:00:00.000 [main] DEBUG com.intuit.karate - request: 1 > GET http://localhost:8080/pet/1 1 > accept: application/json 1 > api_key: special-key 1 > Host: localhost:8080 1 > Connection: Keep-Alive 1 > User-Agent: Apache-HttpClient/4.5.13 (Java/11.0.11) 1 > Accept-Encoding: gzip,deflate 00:00:00.000 [main] DEBUG com.intuit.karate - response time in milliseconds: 49 1 < 200 1 < Access-Control-Allow-Origin: * 1 < Access-Control-Allow-Headers: * 1 < Access-Control-Allow-Credentials: true 1 < Access-Control-Expose-Headers: * 1 < Content-type: application/json 1 < Content-Length: 315 1 < Date: **** 1 < Connection: keep-alive {"name":"laboris Ut","photoUrls":["elit ven","magna sint fugiat in occaecat","sit velit irure proident"],"id":6533262285796651000,"category":{"id":-5086239707500454000,"name":"jDU6rd4mMXrFOzsdIBp"},"tags":[{"id":-4193826791138492400,"name":"Duis anim"},{"id":-7590066944733139000,"name":"elit Ut"}],"status":"sold"} --------------------------------------------------------- feature: api-test.feature scenarios: 1 | passed: 1 | failed: 0 | time: 0.3456 --------------------------------------------------------- 00:00:00.000 [main] INFO com.intuit.karate.Suite - <<pass>> feature 1 of 1 (0 remaining) api-test.feature Karate version: 1.3.0 ====================================================== elapsed: 1.57 | threads: 1 | thread time: 0.35 features: 1 | skipped: 0 | efficiency: 0.22 scenarios: 1 | passed: 1 | failed: 0 ====================================================== HTML report: (paste into browser to view) | Karate version: 1.3.0 file:///${PWD}/target/karate-reports/karate-summary.html =================================================================== stepci テストシナリオ % cat api-test.yaml version: "1.1" name: sample env: host: ローカルのIPアドレス:8080 tests: getPet: steps: - name: GET /pet/1 http: url: http://${{env.host}}/pet/1 method: GET headers: accept: application/json api_key: special-key check: # http statusが200であることをチェックする status: 200 テスト実行 用意されているdockerイメージ を利用することで実行することができる Privacy に記載されている通り利用状況の収集を無効化するため STEPCI_DISABLE_ANALYTICS を指定している % docker run -it --rm -e STEPCI_DISABLE_ANALYTICS=yes -v ${PWD}:/tests ghcr.io/stepci/stepci:2.5.6 tests/api-test.yaml PASS getPet Tests: 0 failed, 1 passed, 1 total Steps: 0 failed, 0 skipped, 1 passed, 1 total Time: 0.216s, estimated 0s Workflow passed after 0.216s Give us your feedback on https://step.ci/feedback 調査しなかったツールたち 候補としては上がったが調査しなかったツールたちです。 ツール URL ライセンス cURL 公式サイト , Github ? HTTPie 公式サイト , Github BSD-3-Clause license Postman + Newman Postmanの公式サイト , NewmanのGithub NewmanはApache License 2.0 insomnia 公式サイト - api fortress 公式サイト - Assertible 公式サイト - speedscale 公式サイト - Datadog 公式サイト - Frisby 公式サイト BSD 3-Clause SuperTest Github MIT license Chakram 公式サイト , Github MIT license REST Assured Github Apache-2.0 license Pact 公式サイト , Github pact-goはMIT license Dredd 公式サイト , Github MIT license まとめ API テストの自動化ツールを調査してみました。運用していないのでどういう問題が発生するかわからないのですが、導入するのはすんなりいけそうなツールがそこそこあるなという印象です。また、冒頭に記載した通りまだチームメンバーに展開していないのでどれを採用するのかはわからないのですが、複雑なことをする予定はないので個人的にはシンプルで簡単にできそうな stepci がいいのではと思っています。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
はじめに JJUG CCC とは 発表内容 資料 テーマの選定理由 さいごに はじめに JJUG CCC とは 日本Javaユーザーグループ(Japan Java User Group/JJUG) が主催として、年2回(春と秋)に開催する1日カンファレンスです。 今回はオンラインで開催され、各セッションは事前録画したビデオの放映とライブQ&Aという構成でした。 このイベントは過去にも弊社のエンジニアが数名登壇したことがあり、ちょうど発表できそうなネタもあったので、今回登壇にチャレンジしました。 あと弊社はロゴスポンサーもやってました(終わってから言っても遅い気がしますが...) ccc2022fall.java-users.jp 発表内容 資料 speakerdeck.com JJUG の YouTube チャンネル でも公開されています。他の方の発表も見返せるので、ご興味があれば是非ご覧ください! テーマの選定理由 「何か新しい技術/概念」を期待されていた方には申し訳ないのですが、今回の発表はモダンな開発をすることによるメリットを、 脆弱性 対応という観点から改めて整理したものになります。(モダンといってもめちゃくちゃ新しいわけではないですが…) 発表のポイントになる「Docker / コンテナ技術」や「テストコードを書くこと」は、今から開発を始めるなら当たり前と言って良いのではないかと思います。しかしそれ以前ではアプリケーションがサーバ上で直接動いているため依存ライブラリの更新に手間がかかったり、テストコードがなく何をやるにしても手動でテストするしか品質を担保できないこともありました。そういった状況下では、発表でも触れたように、 脆弱性 対応が億劫になってしまい「積極的にはやりたくない…」という意識になりがちです。 そうではなく、もう少しポジティブというかシンプルに「 脆弱性 が出たならバージョンアップすればいい」くらいの考えでやっていければ、みんながちょっとずつ幸せになれるのではと思い、このテーマを選定しました。 さいごに 最後に今回の登壇を振り返って締めようと思います。 弊社では技術イベントに登壇する際は、会社からのバックアップを受けることができます。もちろん今回もバックアップを受けており、登壇が実は初めてだった私にとっては非常にありがたかったです。CfPの作成からビデオ録画までの段取りなど、本当に助かったという思いでいっぱいです。 また、イベント当日は「ビデオは提出したし、出番まで他の人の発表聞いてゆっくりしようか」とでも思っていたのですが、いざ出番が近づいてくると結構緊張してドキドキしていました。とはいえホントの直前には「今からビデオを再収録できるわけでもないか」と一周回って落ち着くことが出来ました。ライブでの出番がQ&Aだけということに一番安堵した瞬間でした。 登壇に向けてやることが多くて大変でしたが、その分いい経験になりました。またいつか機会があればチャレンジしてみても良いかもしれません。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは! ラク ス入社1年目の koki _matsuraです。 本日は、 Recoilの基本的な状態管理や仕組み をTodoアプリ作成を通して、ご紹介させていただきます。 こちらの記事は 「Reactの状態管理ライブラリ基礎学習」の3部目 です。 「Redux編」「Redux-Toolkit編」もあるので、下記のリンクから読んでいただけると嬉しいです。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp Reactの状態管理ライブラリを勉強している方、状態管理ライブラリについて簡単に知りたい方などのお役に立てればなと書かせていただきました。 アジェンダ は以下の通りです。 Recoilとは 概要 構成図 Todoアプリ作成 仕様説明 プロジェクト作成 初期設定 ディレクトリ構成 Todo型の定義 Keyの定義 Atomの定義 TodoContainer.tsxの定義 TodoPresenter.tsxの定義 RecoilRootの定義 アプリの起動 Selectorの定義 Todoの追加機能 Todoの削除機能 完了・未完了の切り替え機能 終わりに Recoilとは 概要 RecoilはContextAPIが抱える レンダリング やコード分割の問題を解決するために提唱された、現時点では実験的な状態管理ライブラリであり、アプリケーションの状態を Atom とSelectorの集まりで管理します。 Atom はStateの単位で一意のキーとデータで管理、Selectorは Atom や他Selectorを受け取る純粋な関数です。 Atom を加工して取得したりする目的で用いられます。 Hooks API を使って、状態管理を行う点がRecoilの特徴です。 構成図 Recoilは下図のような仕組みの状態管理をしています。 コンポーネント からHooks API を使用することで簡単に Atom やSelectorの値を取得、変更、更新することができます。 Reduxを使ってから、Recoilを使ってみると、かなり単純で使いやすいことに気づきます。 Todoアプリ作成 仕様説明 Todoアプリを作成する前にTodoアプリの仕様と構成を説明します。 構成は以下の画像のようになります。 入力フォームと送信ボタン、Todoのリストを載せる部分で構成されます。 また、それぞれのTodoには内容に加え、完了ボタン、削除ボタンがあります。 仕様を説明します。 ・Todoの追加 画像上部のタイトル・内容の入力フォームに適当なテキストを入力し、送信ボタンを押すことでTodoリストに入力したTodoが追加されます。 ・Todoリストの表示 画像下部のTodoリストは古いもの(ID昇順)から順に表示されます。最も新しいものは最後尾に表示されます。 ・Todoの完了 それぞれのTodoについている完了ボタンを押すと、該当するTodoが未完了から完了に変化します。 また、完了しているTodoには「戻す」ボタンが表示されており、これは完了ボタンの逆の働きをします。 ・Todoの削除 それぞれのTodoについている削除ボタンを押すと、該当するTodoがリストから削除され、表示からも消えます。 以上が今回作成していくTodoアプリの仕様になっています。 プロジェクト作成 プロジェクトの作成は下記のコマンドを入力します。 私はプロジェクト名を「recoil-todo」としましたが、お好きなプロジェクト名をつけていただいて問題ありません。 npx create-react-app [プロジェクト名] --template typescript 初期設定 プロジェクト作成後、下記のコマンドでプロジェクトに移動して、Recoilを使えるようにします。 cd recoil-todo npm i recoil ディレクト リ構成 Recoilを用いるときの ディレクト リ構成は以下のようにします。 common ディレクト リ、features ディレクト リ、features ディレクト リの中にtodos ディレクト リを作成します。 common ディレクト リ recoilKeys.tsの新規作成 todo.type.tsの新規作成 「recoilKeys.ts」にはRecoilの状態管理で必要となる Atom やSelectorのユニークなキーを格納します。キーについては後ほど詳しく書かせていただきます。 「todo.type.ts」は今回のTodoアプリで出てくるTodoのタイプを定義します。 features/todos ディレクト リ TodoContainer. tsx の新規作成 TodoPresenter. tsx の新規作成 todoState.tsの新規作成 「TodoContainer. tsx 」はTodoアプリのロジック部分を、「TodoPresenter. tsx 」の表示部分を担当します。 「todoState.ts」には状態を管理する Atom と Atom を加工するSelectorを定義します。 Todo型の定義 Todo型を下記のように定義します。 //todo.type.ts export type Todo = { id: number , title: string , content: string , isCompleted: boolean } Keyの定義 Keyは Atom やSelectorに必須です。これは、 Atom やSelectorにおける特定の高度な API に使用されるため、複数の Atom が同じキーを持つことは禁止されています。 今回の場合はTodoを管理する Atom が一つ、それを加工するSelectorが一つのため、Keyが重複する心配はそれほどありませんが、今後、状態が増えたりする可能性がある場合に重複させないためにもKeyを一元管理させます。 「recoilKeys.ts」に Atom のKeyを下記のように一つ定義します。 //recoilKeys.ts export const AtomKeys = { "TODOS_STATE" : "todosState" } Atom の定義 Keyを定義できたので、「todoState.ts」に Atom を定義します。 Atom で定義するものはKeyとStateのみです。Reducerのようなものは書きません。 //todoState.ts export const todosState = atom ( { key: AtomKeys.TODOS_STATE , default : [ { id: 1 , title: "テスト1" , content: "テスト1の内容" , isCompleted: false } , { id: 2 , title: "テスト2" , content: "テスト2の内容" , isCompleted: false } ] as Todo [] , } ); TodoContainer. tsx の定義 このファイルではTodoアプリのロジック部分を担当します。 RecoilではStoreのようなものはなく、 Atom 単位で取得してきます。 Atom の取得だけしたいときには「useRecoilValue」を、 Atom の変更だけしたいときには「useSetRecoilState」を使います。 また、useStateのように取得と変更を両方したいときには、「useRecoilState」を使います。 今回は一旦、Todoの表示だけを実装したいので「useRecoilValue」を使います。後にTodoの追加機能の際、書き換えます。 TodoPresenterはまだ定義していないのでエラーが出ていても問題ありません。 //TodoContainer.tsx import { useRecoilValue } from "recoil" import { todosState } from "./todoState" export const TodoContainer = () => { const todos = useRecoilValue ( todosState ); const args = { todos , } return < TodoPresenter { ...args } / > } TodoPresenter. tsx の定義 このアプリではTodoアプリの表示部分を担当します。 Todoリストを表示します。 まずは、色々な機能を作る前に基盤を作りたいので下記のようなコードにします。 //TodoPresenter.tsx import React , { useState } from "react" import { Todo } from "../../common/todo.type" type TodoPresenterProps = { todos : Todo [] } export const TodoPresenter : React.FC < TodoPresenterProps > = ( { todos , } ) => { const [ title , setTitle ] = useState ( "" ); const [ content , setContent ] = useState ( "" ); return ( <> < form > < label > タイトル: < input type= "text" value = { title } onChange = { e => setTitle ( e.target.value ) } / > < /label > < label > 内容: < input type= "text" value = { content } onChange = { e => setContent ( e.target.value ) } / > < /label > < button type= "button" > 送信 < /button > < /form > < div >------------------------ - < /div > < h1 > Todoリスト < /h1 > { todos.map (( todo : Todo )=> { return ( < React.Fragment key = { todo.id } > < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' > { todo.isCompleted ? "戻す" : "完了" } < /button > < button type= 'button' > 削除 < /button > < /React.Fragment > ) } ) } < / > ) } 入力部にはタイトルと内容の入力フォームとまだ機能のついていない送信ボタンを配置しています。 出力部にはTodoリストをmap関数で出力しています。それぞれのTodoにつくボタンも現時点では機能がついていません。 一旦、これで置いておきます。 RecoilRootの定義 Stateを共有したい コンポーネント をRecoilRootで囲むことで簡単にその コンポーネント をルート コンポーネント にしてStateを共有できます。 「App. tsx 」を書き換えます。 //App.tsx import React from 'react' ; import { RecoilRoot } from 'recoil' ; import { TodoContainer } from './features/todos/TodoContainer' ; function App () { return ( < div className = "App" > < RecoilRoot > < TodoContainer / > < /RecoilRoot > < /div > ); } export default App ; アプリの起動 下記のコマンドで起動してみましょう。 npm start 自動で開くと思いますが、開かない方は「 http://localhost:3000/ 」にアクセスしてください。 次のようにStateを定義したときに入れたサンプルデータが2件分、表示されていればうまくいっています。 Selectorの定義 新しいTodoを作成するときに必要となるIDはその時点のtodosが持つTodoの最大のIDにプラス1した値を割り当てます。 Selectorを使って、最大のIDを取り出します。 まずは、「recoilKeys.ts」にSelectorのKeyを定義します。 //recoilKeys.ts export const SelectorKeys = { "TODO_MAXID" : "todoMaxId" } 次に、「todoState.ts」に最大IDを取得するSelectorを定義します。 //todoState.ts export const maxIDSelector = selector < number >( { key: SelectorKeys.TODO_MAXID , get : ( { get } ) => { return get( todosState ) .length ? get( todosState ) .slice ( -1 ) [ 0 ] .id : 0 } } ) Selectorは Atom を加工して取得する以外に、 Atom の値を変更することも可能です。 Todoの追加機能 送信ボタンを押すと、Todoを追加できるようにします。 手順を説明します。 Container内のuseRecoilValueをuseRecoilStateに変更 Containerで Atom にTodoを追加する関数を作成 Presenterで送信ボタン押下時に2で作成した関数を実行する 「todoContainer. tsx 」でuseRecoilValueを取得と変更を共にできるuseRecoilStateに変更します。 useStateと同じ書き方です。 //TodoContainer.tsx const [ todos , setTodos ] = useRecoilState ( todosState ) Atom にTodoを追加する関数「addTodo」を作成します。また、この際に先ほど作成したmaxIDSelectorを使います。 下記のコードを追加します。 //TodoContainer.tsx const maxID = useRecoilValue ( maxIDSelector ); const addTodo = ( title: string , content: string ) => { const newTodo: Todo = { id : maxID+ 1 , title: title , content: content , isCompleted: false } setTodos ( [ ...todos , newTodo ] ) } Selectorの使い方は Atom と同じです。今回は取得のみなので、useRecoilValueにしました。 今までの状態管理ライブラリでは、dispatchでReducerにActionを送っていましたが、RecoilはsetTodosに新しい状態を格納するだけで更新できます。 argsにaddTodo関数を追加して、「TodoPresenter. tsx 」に渡しましょう。 「TodoPresenter. tsx 」では、送信ボタンを押下時にaddTodo関数を実行するようにしたいです。 なので、addTodo関数を実行し、その後に入力内容を空にするsendTodo関数を作成します。その関数を送信ボタン押下時に実行させるように下記のコードを「TodoPresenter. tsx 」に追加します。 //TodoPresenter.tsx const sendTodo = () => { addTodo ( title , content ); setTitle ( "" ); setContent ( "" ); } //省略 < button type= "button" onClick = { () => addTodo ( title , content ) } > 送信 < /button > Todoを追加できるようになっていれば問題ありません。 Todoの削除機能 それぞれのTodoについている削除ボタンを押すと、リストから削除されるようにします。 Atom からTodoを削除する関数「addTodo」を作成します。 下記のコードを追加します。 //TodoContainer.tsx const removeTodo = ( id: number ) => { setTodos ( todos.filter (( todos ) => todos.id !== id )) } フィルターを用いて、対象のIDをもつTodoだけを弾いた新たなtodosを格納させるコードにしました。 argsにremoveTodo関数を渡して、「TodoPresenter. tsx 」では、削除ボタンを押したときに削除したいTodoのidを引数にしてremoveTodo関数を実行するようにします。 下記のように「TodoPresenter. tsx 」の削除ボタンを変更してください。 //TodoPresenter.tsx < button type= 'button' onClick = { () => removeTodo ( todo.id ) } > 削除 < /button > 削除ボタンを押すことでTodoを削除できるようになっているかと思います。 完了・未完了の切り替え機能 それぞれのTodoについている完了ボタンを押すと、タイトルの横の「未完了」テキストが「完了」テキストになるようにします。また、完了ボタンは「戻る」というテキストのボタンに変化します。 この戻るボタンを押すと、完了ボタンとは逆の操作をします。 「TodoPresenter. tsx 」の完了ボタンとタイトル横のテキストのコードを見てみると、todo.isCompletedで切り替えられることがわかります。 なので、isCompletedを切り替えられる関数を作りましょう。 //TodoPresenter.tsx < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' > { todo.isCompleted ? "戻す" : "完了" } < /button > 「todoContainer. tsx 」に完了・未完了を切り替える関数「toggleComplete」を作成します。 下記のコードを追加します。 //TodoContainer.tsx const toggleComplete = ( id: number ) => { const newTodos = todos.map ( todo => todo.id === id ? { ...todo , isCompleted: ! todo.isCompleted } : todo ) setTodos ( newTodos ) } argsにtoggleComplete関数を渡して、「TodoPresenter. tsx 」では、完了ボタンを押したときに対象のTodoのidを引数にしてtoggleComplete関数を実行するようにします。 下記のように「TodoPresenter. tsx 」の完了ボタンを変更してください。 //TodoPresenter.tsx < button type= 'button' onClick = { () => toggleComplete ( todo.id ) } > { todo.isCompleted ? "戻す" : "完了" } < /button > 完了ボタンを押すと、それぞれのTodoタイトルの横の「未完了」が「完了」に切り替わることが確認できると思います。 終わりに Recoilを用いたTodoアプリの作成を通して、基本的な使い方や仕組みをご紹介させていただきました。 Reduxのように一箇所に状態を集めて管理する方法ではなく、 Atom やSelectorという単位で状態を管理することで更新のたびにアプリケーション全体の状態を上書きする必要がなくなりました。 また、状態の操作をReducerではなく、Hooks API を使って行うのでState側で定義することがかなり減ったと思います。 ただし、懸念点として小規模なアプリケーションではかなり使いやすいですが、大規模なアプリケーションになるとContainer側で状態を操作できるというのは意図しない状態更新を行うことを可能にしてしまうということです。 そのため、直接 Atom やSelectorを操作するのではなく、カスタムフックを用いて操作するなどの対策をとることで大規模なアプリケーションでも安全に使えるのかなと思います。 このような問題点をリファクタ編として、解決策とともに下記の記事にまとめさせていただいたので、お読みいただけると嬉しいです。 tech-blog.rakus.co.jp ここまで読んでいただきありがとうございました。 この記事がRecoilを使いたい方や、Reactの状態管理ライブラリについて知りたい方の助けになれれば幸いです。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは!2022年度新卒で楽楽精算開発課に配属されましたtarayamaaaと申します。 今回は私たち 楽楽精算開発課に配属された新卒が実務に入る前に行う学習メニュー について紹介させていただこうと思います。 こちらの学習メニューの一部は、新卒だけでなく中途の方も行う内容となっております。 そのため、学生や新卒の方だけでなく中途の方にも参考になりましたら幸いです。 目次 目次 ラクスにおける新卒研修について 楽楽精算開発課の学習メニュー 主な学習の流れ 学習の具体的な内容 その他の学習メニュー(抜粋) 実際に取り組んでみて 終わりに ラク スにおける新卒研修について 基本的に ラク スに入社した新卒は、4月から6月まで東京と大阪を含めた新卒全員で本社で研修を受けることになります。 おおよそ4月の中旬までビジネスマナーや商材の研修を受講し、その後6月末まで Java などの基本的な技術の研修を受講する流れとなっています。 ※ ラク スの新卒研修の詳細については、以下の記事をご覧ください。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp 楽楽精算開発課の学習メニュー 上記の研修を受講した後はいよいよそれぞれの部署に配属され、配属先で使う技術についての学習メニューを受講後実務に入るといった形になっています。 配属先によって使用する言語であったり技術スタックが微妙に異なるため、そのあたりの知識や業務のための ドメイン 理解に近い内容を学ぶことになります。 楽楽精算開発課では主に以下のような流れの学習メニューを導入しています。 主な学習の流れ 楽楽精算にどのような機能があるのかを知る 楽楽精算の何が楽なのかを体験する 楽楽精算の開発を疑似体験する 学習の具体的な内容 楽楽精算にどのような機能があるのかを知る サポートサイト学習 楽楽精算のサポートサイトと楽楽精算の検証環境をもとに演習形式で楽楽精算にどのような機能があるのか、楽楽精算のたくさんあるオプションも含めた機能で様々な会社のケースに対応するにはどのように設定や機能を使用すると手間なく精算・ 経理 作業を行うことができるのかといったことを学びます。 最終的に用意されたテストを9割解けるようになるまで学習することになるのですが、ほぼ確実に楽楽精算のサポートサイトで存在するほぼすべてのページを読んで理解することになります。 (ただし、ただ読むだけではなく実際に検証環境があるため、自分の感じた疑問点を実際に動かして確認しながら進めていくことができます。) 楽楽精算の何が楽なのかを体験する 楽楽精算の意義理解 製品説明資料作成 楽楽精算の意義理解とは、その名の通り楽楽精算の意義の理解を目的として、楽楽精算を使用した場合と使用しなかった場合の両方を体験し、どのような点で楽楽精算が精算・ 経理 作業が楽になるように貢献しているのかを学びます。 実際に会議室でメンターの先輩方にご協力いただいてお題として用意された仮の顧客訪問をもとに精 算額 の算出から仕訳・確定作業までを楽楽精算を使用せずに行い、 その後楽楽精算で同じ作業を行うことによって、サポートサイトで学んだ機能によって誰がどのように楽になるのかといった点を顧客視点で理解します。 また、最後に学びのまとめとして製品の説明資料を5時間で作成し、課の先輩方に10分間の発表を行います。 ここでは理解の確認だけでなく、先輩方から質問も受けるため理解の甘い点についても考えを深めることができます。 楽楽精算の開発を疑似体験する 開発の疑似体験 既存の楽楽精算に実際に機能を追加し、 JUnit を用いたテストコードの作成や 単体テスト までを疑似的に体験します。 同様の機能のコードや仕様書を参考に実装し、楽楽精算の権限管理やクラスの 命名規則 といった開発のルールや仕組みの理解とともに、実際に今後所属するチーム内でどのような流れで開発が行われているのかを習得することを目的としています。 機能の追加後はメンターの先輩にマージリク エス トベースでコードレビューをしていただき、考慮漏れがないように確認をもらいOKが出れば 単体テスト に取り掛かります。 単体テスト 項目書は個人ごとに書き方などに差がでやすいものであるため、配属されるチームの手法に合わせるために因子水準の抽出からテストパターンの作成・テストの実行を逐次レビューをいただきながら行います。 このようなメニューを行うことで、実務に入った後にスムーズに仕事の流れをつかむことができるようになります。 上記以外にも新卒配属では、以下のような Java や PostgreSQL の資格の取得など様々な項目について学びます。 その他の学習メニュー(抜粋) 資格取得 Java Silver OSS-DB Silver 技術スキル Webアプリケーション( TCP/IP / HTTP / SSL / Cookie 等) Webサーバ / フレームワーク (楽楽精算の アーキテクチャ 関連) セキュリティ Linux Jenkins Git JUnit Selenium など ヒューマンスキル 課題図書 入社1年目の教科書 以下のうち1冊 3分でわかるロジカル・シンキングの基本 問題解決力を鍛えるト レーニン グブック エンジニアのための伝わる書き方講座 など 実際に取り組んでみて サポートサイト課題 楽楽精算のサポートサイトのほぼすべてのページを読むことになるので、読み終えるまでものすごく時間がかかりました。 時間はかかりますが、読む前と読んだあとではその後の作業の際に初めて使う機能がうまく動かないといった場面でもなんとなくどこの設定が足りないのか、どこを設定すれば使えるようになるのかといった点が以前に比べて推測できるようになったため、製品理解を深めることができました。 細かなオプション機能の使い方などはまだ一部知識が甘いですが、この学習メニューのおかげで通常の精算作業であれば基本的に問題なく行えるようになったと思います。 楽楽精算の意義理解 & 楽楽精算製品説明資料作成・発表 入社して以降楽楽精算でしか精算処理を行ったことがないことと、 経理 作業を実際に行ったことがなかったため、なんとなく頭で楽楽精算を使わないと不便ということは理解していてもどのような点でどのような不便を解決しているのかといった詳細なポイントまでをきちんと理解していませんでした。 そのため、この学習メニューを通してどのような箇所でどの程度時間がかかるのかであったり、ヒューマンエラーがどのポイントで発生しやすいのかといった知識を吸収できた点で非常に面白い体験でした。 楽楽精算の開発の疑似体験 楽楽精算がどのように権限管理をしているのかであったり、複雑なクラス構造などを実務に入ってぶっつけ本番で学ぶのではなく、実務の前に練習として取り組めたという点でとてもありがたい内容でした。 実際に取り組んでみてミスに対する リカバリ ーに時間がかかりすぎてしまったり、テスト項目書を作成する際に不要な因子を追加することによって大量のテストケースになってしまったりしたのですが、実務に入る前にインプットだけでなくアウトプットとして学べてよかったです。 終わりに 以上が楽楽精算開発課における配属後の学習メニューの紹介になります。 体系的にメニューが組まれており、非常にスムーズに楽楽精算の製品理解を進めることができました。 また、楽楽精算開発課では新卒にはメインとサブの2人のメンターについていただけるので、詰まるところや質問があるとすぐ対応していただけてとてもありがたく感じました。 配属されてから学んできたことを今後の実務に活かすとともに、昨年の先輩がもっと時間をかけて学びたかった!という観点でおととしの内容からアップデートされたように、私たち今年度の新卒が感じた点をを来年、再来年の後輩のためにもっと改善していけたら良いなと思います。 ありがとうございました。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター