OmniAICreator/Qiita-1.07M huggingface dataset:
【初心者向け】git pull, git fetch, git pushで何が起きているか図解してみた

# はじめに
最近Gitの勉強をしているのですが、git pullやgit fetch等解釈が難しいと感じたので、図解してみようと思います。
同じくpull, fetch, pushで混乱している人の参考になれば良いなと思います。

分かりにくかったり、誤った記載があれば、コメント欄にて優しく教えていただけると嬉しいです！

# 用語の説明
- リポジトリ：リポジトリとは変更を記録するための場所です。
 - ローカルリポジトリ：自分のPC上のリポジトリ
 - リモートリポジトリ：ネットワーク上のリポジトリ（Github等）


- ブランチ：直近のコミットを指し示すポインタ
  - ローカルブランチ：ローカルにあるブランチ（例：master）
  - リモート追跡ブランチ：リモートブランチの動きを追跡するブランチ（例：origin/master）
  - リモートブランチ：ネットワーク上にあるブランチ（例：master）

# git pull,　git fetch, git pushについて
git pull, git fetch、git pushの動きを大まかに見ていこうと思います。

- git pull
「ローカルリポジトリ」が、「リモートリポジトリ」から情報を更新するコマンドです。
「リモート追跡ブランチ」と「ローカルブランチ」の情報を更新することができます。
- git fetch
「ローカルリポジトリ」が、「リモートリポジトリ」から情報を更新するコマンドです。
**「リモート追跡ブランチ」のみ**情報を更新することができます。
- git push
「リモートリポジトリ」が、「ローカルリポジトリ」から情報を更新するコマンドです。

【イメージ図】
![概要.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799854/a4a4c77e-2bec-d35e-2a2b-007240fd107f.png)

それではもう少し詳しく見ていきましょう！

### git pull
git pullは「ローカルリポジトリ」が、「リモートリポジトリ」から情報を更新するコマンドです。
「リモート追跡ブランチ」と「ローカルブランチ」の情報を更新することができます。
コードは以下の通りです。

```bash
$ git pull リモート名 ブランチ名

# 以下は「リモートリポジトリ」のmasterブランチの情報をpullするときのコマンドです。
$ git pull origin master
```
git pullを使う際は、今いる「ローカルブランチ」とpullしたい「リモートブランチ」が一致していることを確認しましょう。
※例えば上記のコードにおいて、「ローカルブランチ」がmaster以外のブランチ（例えば、branch1とします）の状態で、pullしてしまった場合、branch1がリモートのmasterブランチの情報で更新されてしまいます。

**【git pullのイメージ】**
![git pull.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799854/d5621081-32d1-8269-24a8-a3f57fb4751a.png)

### git fetch
git fetchは「ローカルリポジトリ」が、「リモートリポジトリ」から情報を更新するコマンドです。
**「リモート追跡ブランチ」のみ**情報を更新することができます。
git fetchの後にgit mergeをするとgit pullと同じことが起きます。

```bash
$ git fetch リモート名

# 以下は「リモートリポジトリ」の情報をfetchするときのコマンドです。
$ git fetch origin
```
**【git fetchのイメージ】**
![git fetch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799854/3bcb6267-1d06-c114-6efa-c0fd2e66917a.png)

### git push
git pushは「ローカルリポジトリ」から「リモートリポジトリ」に情報を送るコマンドです。
指定したブランチ（例：master）のみ「リモートリポジトリ」の情報を更新することができます。
※次に出てくる【git pushのイメージ】の通り、指定していないブランチ（図の場合branch）は更新されません。

```bash
$ git push リモート名 ブランチ名

# 以下は「ローカルリポジトリ」のmasterブランチを「リモートリポジトリ」のmasterブランチにpushするときのコマンドです。
$ git push origin master
```

**【git pushのイメージ】**
![git push.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799854/afe156ea-107a-c170-caee-73f56645d3f6.png)
AtCoder Beginner Contest 115 D - Christmas

## 概要

解説ACした問題の[@hiorkihello](https://twitter.com/maxyasuda)の、反省log

## 問題

https://atcoder.jp/contests/abc115/tasks/abc115_d

1~50までレベルが動くNレベルバーガーを作る。

含まれるパティの大きさを求めよ

## 自分の思考


まずDPでレベルがnの時の枚数を求めることができることが思いついた。

また文字列で保存することでnレヴェルのものを簡単に求めることができるとわかったが、普通にやると長さが2 ^ 50となってしまい間に合わない。

どうやって枚数を求めることができるのか思いつかなかった

## 正解で必要な思考

バンズの枚数だけでなく、パティの枚数についてもDPで求めておくことが必要。

その次に必要なのが場合わけである。

解説を見るまで気づかなかったが、これは再帰的に解くことができる。

レベルNバーガーの下からXそうというものは、いずれかの場合で落ち着く。

まずXが下記の場合を考える。

1. レベルNバーガーの一番下の場合→バンズである
2. レベルNバーガーの一番上の場合→バンズである
3. レベルNバーガーのちょうど真ん中の場合→パテである。

これはすぐにわかるだろう。

問題は上記以外だが、それはこのようになる。

4. レベルNバーガーの真ん中より下で一番下でない時。→レベルN-1バーガーのどこかになる(真ん中より下は、バンズ+バンズ＋N-1バーガー)
5. レベルNバーガーの真ん中より上で一番上でない時。→レベルN-1バーガーのパティの枚数(真ん中より下は、バンズ＋N-1バーガー)＋1枚(真ん中)＋レベルN-1バーガーのどこかまで

これらはあえて厳密に書いていないが、想像できると思う。

このどこかだが、再帰的に求めることができる。

まず４の場合を考える。

４の場合は
レベルN-1バーガーのどこかになるが、それは

今回の場合わけしたどこかにまたなることが直感的にわかるだろう。

つまり、今回と同様に5つの場合に分けることができる。その時考慮する高さXは、一番下のバンズをのぞいてN-1レベルバーガーのX-1層目になる。

5の場合、
これも同様にN-1バーガーのどこかになる。

なぜならNバーガーは、B(N-1バーガー)P(N-1バーガー)Bとなっているからである。
よって最終的に求めるパティは、N-1バーガーのパティの枚数＋1(真ん中のパティ)+N-1バーガーのX-(n-1バーガーの大きさ + 1(一番下のバンズ) + 1（真ん中のパティ）)となる。

これを再帰的に求めていけばよい。

なぜ求められるのかというと、B(N-1バーガー)P(N-1バーガー)Bという定義的に、全ての部分がどこかしらで一番下・真ん中・一番上のどこかに当てはまるからである。

## まとめ
DDDについて　備忘録

### DDD（ドメイン駆動開発）で使用される語句
DDDを理解する上で知っておくべき、語句をまとめる。

####ドメイン 
ソフトウェアで問題解決しようとする対象領域のこと

####モデル
問題解決のために、物事の特定の側面を抽象化したもの。ドメインモデルはドメインの問題を解決するためのモデル。

####ユビキタス言語
開発者とユーザーとの間で共通の厳格な意味を持つ用語を構築するというプラクティスを表すために使用した用語である。 ユビキタス言語はソフトウェアにおけるドメインモデルに基づいている。



### DDD（ドメイン駆動設計)とは
DDD（ドメイン駆動設計）はソフトウェア開発手法の1つ。どのような開発手法か簡潔に説明すると、ドメインモデルをソフトウェア開発の中心にすえ、コードやコミュニケーションを常にドメインモデルと一体化させながら、ドメインモデルを反復的に深化させることでより価値の高いアプリケーションを生み出していこうとする手法である。

### DDDとアーキテクチャ
DDDではレイヤードアーキテクチャが用いられている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602844/20686c3d-4cab-cdb0-6062-199ec722a886.png)

#### presentation層
アプリケーション外部との入出力を実現する。JSONでレスポンスを返したり、HTMLをレンダリングして返したりといった選択はこの層の責務。主なクラスは、コントローラー、アプリケーション外部との入出力を定義するクラスがある。

#### usecase層
ドメイン層が公開している操作を組み合わせて、ユースケースを実現させる。特定のクライアントには依存しない実装にする。

#### domain層
ドメイン知識(ルール/制約)を表現する。この層を独立させ、他の層への依存を持たせないようにする。層自体を「ドメインモデル層」「ドメインサービス層」とする方法もある。


#### infrastructure層
下位レイヤで定義されているインターフェイスを実装し、アプリケーションを支える技術的な提供。
zabbix agent 2を入れた

Zabbix2か3をよく使っているけど、気づいたら5が出ていた。
アプライアンスという起動イメージを選んで起動するだけで使えた。
タイムゾーン設定と日本語化くらいしかしていない。
https://www.zabbix.com/download_appliance


エージェントのインストールドキュメントはこのあたり。
https://www.zabbix.com/download?zabbix=5.0&os_distribution=red_hat_enterprise_linux&os_version=8&db=mysql

### zabbix-agent2インストール
```:CentOS8,Mysql
 # rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/8/x86_64/zabbix-release-5.0-1.el8.noarch.rpm
 # dnf clean all
 # dnf install zabbix-agent2
```

### 設定ファイル書き換え
```
# cp -p /etc/zabbix/zabbix_agent2.conf /etc/zabbix/zabbix_agent2.conf.org
# diff /etc/zabbix/zabbix_agent2.conf.org /etc/zabbix/zabbix_agent2.conf
80c80
< Server=127.0.0.1
---
> Server=192.168.11.161
120c120
< ServerActive=127.0.0.1
---
> ServerActive=192.168.11.161
131c131
< Hostname=Zabbix server
---
> Hostname=appliance
```

### 有効化・再起動
```
systemctl start zabbix-agent2
systemctl enable zabbix-agent2
systemctl restart zabbix-agent2
```

### 10050ポートを開ける
/etc/sysconfig/nftables.confに
> tcp dport 10050 ip saddr 192.168.xx.xx/32 accept

を追加して`systemctl restart nftables.service`



zabbixにホスト登録して、アイテムにテンプレートの「Template OS Linux by Zabbix agent」を適用してしばらくまつと、監視がはじまった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75914/5442a69a-cbe7-5e63-8bf3-e9a512b008ed.png)
私のマネージメントがうまく行かなかった3つの原因

# はじめに

リブセンスの転職ナビでエンジニアリングマネージャーをやっている佐藤です。
2020年もそろそろ終わりを迎えようとしていますが、私にとっての2020年はマネージメントについて学ぶことが多かった年でした。
翻って見ると、2019年の私は失敗ばかりしていたように思います。
今回は、その失敗に至った原因について考察していきたいです。
なかなかセンシティブな話題ではあるので、具体的にどんな失敗だったかなどはなしで、純粋に原因部分にだけフォーカスしていきます。
割とこの部分だけでもエンジニアリングマネージャー(特に在籍期間の長い)あるあるな気はしており、この文書がいつか誰かの役に立てば幸いです。

# 背景

私は2015年から転職ナビでエンジニアをやっており(途中長期で育休など取りつつ)、2017年ころからチームリーダーという名の役職についていました。
その頃は一種テックリード的な仕事だったり、メンバーとの1on1だったり、評価に関して上長であるグループリーダーへの助言だったりはしていましたが、それほど明確になにかのマネージメントをしていた意識がありませんでした。

その後2019年初頭から現在に至るまで、転職ナビの開発全体を見るグループリーダーを務めています。
2019年も日々頑張っていたような気はしますが、なんにせよ結果として2019年あまりグループはうまく行っていませんでした。

# 3つの原因

## 忙しかった

はい、初っ端から典型的なやつです。アンチパターンですね。

2015年から在籍してプレーヤーをやっていましたので、当時たくさんの属人化した業務を抱えており、現状を維持するだけで手一杯だったのは事実でした。
同時に、プレイングマネージャーだからといってマネージメントを放棄していいわけではなく、組織の成果が自分の成果であるということも、頭ではわかっていたつもりでした。

ではなぜ忙しくてマネージメントがうまく行かなかったかというと、「どこでどう手を空けるのか」がわからなかったのです。
日々私のところにやってきたタスクたちの裏に、真剣に転職活動をするユーザー、サービス改善に必死な同僚がいると思ったとき、手を空けることは簡単ではありません。
自分のグループのメンバーに委譲しようにも、再現性の低いタスクの内容について共有し解決まで導くのは、どうしても時間がかかる割にメリットが薄いのです。

結果「実質的に自分にしかできないタスク」を日々増やしながら、「採用の成功や経営判断によるリソース増強を待つしかない」と思っていました。

## 何をマネージメントしているのかわかっていなかった

組織をマネージメントするとか、メンバーの価値発揮を最大化するというのは簡単なのですが、そのお題目と実践の間をつなぐ論理がかけていたような気がしています。
一応多少はマネージメント関連の書籍なども読み漁っており、なにかわかったような気がしてしまっていたのも問題だったのだと思います。

いわゆる素人の生兵法というやつで「メンバーの価値発揮を最大化するために」「なんか本に書いてあったイケてる手法を実践する」ような状態でした。
端的に言うと、その時点でのチームの成熟度合いにあった改善を行っていなかったのです。
プログラミングで近しい状態を指す言葉はパターン魔(なんにでもデザインパターンを適用して無用に複雑性をあげる)とかでしょうか。

忙しくてちゃんとマネージメントしてないのに、たまに手を出したときまで的はずれなのではうまくいくはずがありません。

## これまで通りの報酬に満足してしまっていた

上記のような状態であれば、周囲からの風当たりが強くなってリーダーを続けられなくなったりすると思うのですが、そうはなりませんでした。
理由はいくつかあったと思うのですが、その一つにプレイヤーとしての価値発揮があったと思います。

なんのかんのと前述の「実質的に自分にしかできないタスク」をたくさん抱えて日々頑張っていると、感謝もされるしある程度は評価もされます。今から考えると結構ぞっとする状況です。
そこから見える典型的なバッドエンドは「マネージメントの欠如により組織の成果が落ちてきて、いよいよ風当たりが強くなり、退職したら属人化しすぎたタスクが爆発四散」とかでしょうか。

なんにせよ、当時の私は上記のような状態に安住し、なんとしても自分でこの状態を変えようという意識にはなれていませんでした。

# その後どうなったか

異動によってグループが増強されました。
どの程度上記のような状態を汲んだ上での判断だったのかは私にはわかりませんが、シニアエンジニアが2名加入してくれました。
来てくれた2人には感謝しかありません。

結果的には会社からタオルが投げ込まれたような状況です。会社の判断は正しかったと思っています。
しかし、自分としてはせめてこの状況をもっと自発的に「これこれこういう状況だからこういう人を寄越してほしい」と陳情するような形で作っていくべきだったと反省しています。

また、現在ではもうほぼエンジニアリングはしていません。
やりたくないというわけではなく、まだ2足のわらじを履ける状態にないと思っているのです。
もし今のグループがもう少しいい感じで回り始めたら、またエンジニアリングをしっかりやっていきたいと思っています。
もちろん適切な配分で。

こうして2020年は、新体制のもと今のところうまく行っていると思っています。

# 教訓

- 忙しくても常に状況を俯瞰する意識を忘れない
	- 忙しくて頑張ってる認識が強いと、視野狭窄になりがち
	- まずはタスクをこぼしてでも手を空ける必要がある
		- 無理に休出などで時間を作るとさらに視野狭窄になるので絶対やめるべき
- もっと組織を信頼する
	- 自分を責めすぎる必要はないが、うまく行っていない状況は認めて改善のアクションを起こす
	- 自分一人ではできないにせよ、上手に弱音を吐く、組織の力を使う
	- 私の場合は幸い組織がしっかりしていて助かったが、それでもダメなら潔く転職しよう

# おわりに

この文書が、2019年の私と同じような状況に陥っているエンジニアリングマネージャーの方の参考になれば幸いです。
Railsアプリケーション全体のジェネレーターの設定変更方法

Railsアプリケーション全体のジェネレータの設定変更方法について、調べながら学習したのでここにアウトプットさせていただきます。

- 開発環境
    - Ruby 2.6.4
    - Rails 5.2.3
   
# 設定方法
Railsアプリ全体のジェネレーターの設定を変更するには、`config/application.rb`ファイルに設定コードを追記してあげると変更する事ができます。

`application.rb`ファイルの`class Application < Rails::Application`の記述下に、

```
config.generators do |g|
end
```
以上のコードを追記し、`config.generators do |g| 〜 end`の間に設定コードを追記します。

```
config.generators do |g|
 #ここに設定コードを追記する
end
```

今回は`Rails generate controller`でファイルを自動生成する際に、`asset, helper, testファイル, ルーティング`が生成されない様に制限を設けたかったので、以下の４行のコードを追記しました。


```
config.generators do |g|
 g.assets false　#assetsを生成しない
 g.helper false　#helperを生成しない
 g.test_framework false　#testファイルを生成しない
 g.skip_routes true　	#ルーティングを生成しない
end
```
これでRailsアプリケーション全体のジェネレーターの設定を変更する設定ができました。

あとはこの状態で`rails g controller user show` の様なジェネレーターコマンドを入力すると、

![スクリーンショット 2020-11-23 21.11.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574875/75c5429d-9f88-728e-bbe0-93478029ff35.png)

上記の様に必要なディレクトリやファイルだけを生成する事ができます。

# 終わり

以上でアウトプットを終わります。
最後まで読んでいただきありがとうございました。
少しでもお役に立てれる内容であれば幸いです。
SwiftUIだけど画面遷移はUIKitでやる

画面遷移処理を各画面から切り離したり、カスタムURLスキームなどを使って任意の画面に遷移できるようにする処理をSwiftUIでもやりたい。でもSwiftUIだけで実現する方法がわからない。。。
難しそうなところは今後のSwiftUIの進化に期待するということで、画面遷移は無理せずUIKitベースでやってしまえば良さそうだなと思い始めました。

### 環境

* Xcode 12.0

### 実装

各画面のレイアウトはSwiftUIでさくっと作ってしまって画面遷移に関連する部分はUIKitベースで処理するために、遷移先はUIHostingControllerを使う。ViewControllerを見つける処理は従来通り。

```swift
var window: UIWindow? {
    guard let window = (UIApplication.shared.connectedScenes.first?.delegate as? UIWindowSceneDelegate)?.window else { return nil }
    return window
}

/// 全面に表示されているViewControllerを見つける
func topViewController(_ vc: UIViewController? = nil) -> UIViewController? {
    guard let vc = vc ?? window?.rootViewController else { return nil }
    if let presented = vc.presentedViewController {
        return topViewController(presented)
    }
    return vc
}

/// NavigationControllerを見つける
func navigationController(_ vc: UIViewController) -> UINavigationController? {
    if let result = vc as? UINavigationController {
        return result
    }
    for child in vc.children {
        if let result = navigationController(child) {
            return result
        }
    }
    return nil
}

@main
struct MainApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL(perform: { _ in
                    guard let topVC = topViewController() else { return }
                    if let navVC = navigationController(topVC) {
                        navVC.show(UIHostingController(rootView: TestView()), sender: nil)
                    } else {
                        topVC.present(UIHostingController(rootView: NavigationView(content: { TestView() })),
                                      animated: true,
                                      completion: nil)
                    }
                })
        }
    }
}
```

SwiftUIで作った画面をXcodeのDebug View Hierarchyで見てみると、ViewControllerらしきコンポーネントがたくさん使われているようだったので、上記の実装は「NavigationViewを使えばUINavigationControllerが内部的には使われているかもしれない」とか、「sheetでモーダル表示したらpresentedViewControllerで遷移先のViewControllerを見つけられるかもしれない」という思い込みで実装してみました。
SwiftUIのNavigationViewを使っている場合にUINavigationControllerを探索可能かどうか不明でしたが、UINavigationControllerを継承していそうなクラスが使われているようでした。
Google ColaboratoryでRを使う方法

#Google ColaboratoryでRを使う方法の備忘録

1.　[Google Colaroratory](https://colab.research.google.com/notebooks/intro.ipynb#recent=true)にアクセスして「ノートブックを新規作成」をクリック。

![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471633/ab23dd56-851b-f839-e09b-ec03fdedd4b1.png)

2.　以下の画面が表示される。

![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471633/b3a6fcdf-4d6b-4bf2-5394-c598eb671106.png)

3.　「ipynbをダウンロード」を選択する。

![図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471633/937251de-be74-abe2-a603-e29fa90335c9.png)

4.　ファイルを保存する。

![図4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471633/93b16e87-e7f7-e34e-2fbc-425ca5ac7c62.png)

5.　保存したファイルをメモ帳などのテキストエディタで開く。以下のように、``"kernelspec"``内を書き換える。

![図5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471633/75ddc449-b6de-67e6-8cbb-926bec0d36d0.png)


6.　書き換えたファイルを上書き保存し、「ノートブックをアップロード」する。

![図6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471633/f7ca515c-d98b-cf71-3243-15d03b84bbeb.png)

7.　アップロードが完了するとノートブックの画面が表示される。「ランタイム」→「ランタイムのタイプを変更」を選択する。

![図7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471633/8c423155-f182-dd82-e5b8-c5d95628f4af.png)

8.　ランタイムのタイプが「**R**」となっていることを確認する。これで**R**を使うことができる。

![図8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471633/3e7de9cc-5cc6-6c48-0c5e-e3a31f824259.png)

#実際に使ってみた

**tidyverse**ライブラリを読み込み、**iris**データで散布図を試しに書いてみた。

![図9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471633/61ff3653-508b-9b3b-8168-d1484aa3a57b.png)
【Node.js  ~環境構築~】勉強メモ

開発環境用意
-
- Node.jsが既にインストールされているか確認

ターミナルで下記のコマンドを実行

```
~ % node -v
```
`v12.13.1` のようにバージョンが表示された場合は、既にNode.jsがインストール済み
`command not found: node` と表示された場合は、まだインストールされていない。
<br>

- Node.jsのインストール

Node.jsのインストーラーは公式サイトからダウンロード。
[Node.js公式サイト](https://nodejs.org/ja/download/)
今回は、推奨版をダウンロード。
後は、OSに合わせて、Windows及びmacを選択。
![image](https://i.gyazo.com/d855f271e4ebe0d2baeb98c01a0605f7.png)
ちなみに、Node.jsに加えて、npmもインストールされる。
（ダウンロードの下に同梱npmと記載あり）
npm (Node Package Manager)とは、パッケージを用意する為のシステム、
Railsでいうとgemみたいなものでしょうか。
<br>

- 最後に、Node.js及びnpmが正常にインストールができているか確認

```
~ % node -v

~ % npm -v
```
ここまでで、Node.jsのインストールは完了、
次からは、Node.jsを実際使用して、アプリケーションを作成する為の準備の流れ
<br>

アプリケーション作成準備
-
- 新規アプリケーション用のフォルダを作成

どこでも良いのでファルダを作成する。
今回は、こんな感じで作ったとする。
/Users/ユーザ名/projects/practice

そして、ターミナルのcdコマンドを使用して、practiceフォルダ迄移動する。

```
sample@sampleMBP ~ % cd
sample@sampleMBP ~ % cd projects
sample@sampleMBP projects % cd practice
sample@sampleMBP practice %
```
<br>

- パッケージをインストール

必要なパッケージをインストールします。

まず以下のコマンドを移動したフォルダ、今回ならpracticeフォルダで実行

```
practice % npm init --yes
```
これは、npmの設定ファイルであるpackage.jsonを生成するコマンド。
package.jsonには、npmパッケージの設定情報などが書き込まれていく。
<br>
次に、npmパッケージのインストールをする。
今回は、expressと ejs というパッケージをインストールする。

以下のコマンドを実行

```
practice % npm install express ejs
```
※`npm install express` `npm install ejs`と1つずつinstallコマンドを実行していくことも可能
<br>

サーバーを起動してページ表示
-
これでひとまず必要なパッケージは揃い、
また必要なファイルは既に作成されています（◯◯◯.jsonというファイルが２つ程作成されています）

後は、viewファイルと、JavaScriptを記載するファイルの作成
viewファイルは、名前はなんでも良いが、拡張子は`.ejs`
例：sample.ejs

JavaScriptを記載するファイルの拡張子は`.js`
例：app.js

これでファイルの準備は完了。
最後に、サーバーを起動する下記のコマンドをターミナル上で打つ。

```
practice % node app.js
```
<br>
これでファイルの中見をちゃんと記載していれば
正常にブラウザ上でアプリケーションが起動する。
README に記述するアプリ開発の背景

#はじめに
個人開発したオリジナルアプリのREADMEを記述しています。このアプリを開発した背景や問題、課題を記載しました。問題と背景の切り分け、考え方の本質を書き記します。

#目次
1.問題と課題の違い
2.オリジナルアプリの開発背景から設定した問題と課題

#1.問題と課題の違い
辞書などにおける意味は下記の通り。言葉そのもの意味に差異はない。

問題：批判・論争・研究などの対象となる事柄。解決すべき事柄。課題。
課題：解決しなければならない問題。果たすべき仕事。

参考：
[課題](https://dictionary.goo.ne.jp/word/%E8%AA%B2%E9%A1%8C/#jn-41674)
[問題](https://dictionary.goo.ne.jp/word/%E5%95%8F%E9%A1%8C/#jn-220806)

しかし、ビジネスにおいては明確な違いがある。主に下記のような意味で使われる。

問題：好ましくない状態
課題：問題を解決すべく行うこと

問題と課題の因果関係は上位に問題、下位に課題となる。基本的には1つの問題に対し、複数の課題がぶら下がる。逆算的に見れば複数ある下位の課題を全て解決すると、上位の問題も解決されることになる。

#オリジナルアプリの開発背景から設定した問題と課題
**背景**
コロナ渦をきっかけとしてリモートワークが増えており、それに伴い自宅にいる時間も増えてる。普段の職場における労働と比べ、具体的には下記2点の傾向が増加していると考えた。

・一人で部屋にこもりがち。
・仕事とプライベートの切り替えが曖昧。これまで仕事をしない時間帯や場所で仕事をする。


**問題**
下記2点の理由から「**メンタルヘルスが悪化する可能性がある**」を問題として設定した(状態)。

・人との接点が減り孤独を感じやすくなる
・デスク周りの汚れや整理整頓に対し、他者から指摘されない。部屋が汚く無意識にストレスを感じる。

**課題**
メンタルヘルスが悪化するという問題に対し、下記2点を課題とした(やること)。
・対面に変わる、孤独を感じない仕組みの確立
・清潔が保たれている空間の確保
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703987/d61c5db7-9d3c-ac13-ab36-c93af350d4fe.png)

そして各課題をさらに細かく分類し、具体的な機能を7つ抽出した。逆算的にみれば抽出した7つの機能を満足することにより2つの課題は解決され、最終的には問題も解決できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703987/9cc19814-6e3e-602b-a556-a5f61a708758.png)

以上
Djangoで外部の既存データベースを参照する

## 概要
* 既存でデータベースを運用していたが、新規で構築するDjangoアプリケーションからそのデータベースを参照したかった。
* DjangoのデフォルトDB以外も参照できる。
* Mysqlでしか試していないがPostgresも同様にできると思われる。

## 設定方法
### DB情報を追記
* DATABASES が辞書形式になっているので追記する。

```settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': db,
        'USER': user,
        'PASSWORD': password,
        'HOST': 'mysql01',
        'PORT': '3306',
    },
    'otherdb': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': otherdb,
        'USER': user,
        'PASSWORD': password,
        'HOST': 'mysql02'
        'PORT': '3306',
    }
}
```

### モデルを設定
* 通常と同様にモデルを定義する。
* メタデータとして「db_table」に参照したいDB内のテーブル名をしてする。
    * 指定しないと「<アプリケーション名>_<クラス名>」の形で表示された。（デバッグで確認）

```application/models.py
from django.db import models

class <クラス名>(models.Model):
    class Meta:
        db_table = '<テーブル名>'
    id = models.IntegerField(primary_key=True)
    name = models.CharField(max_length = 255)
```

### ビューの設定
* 「db_manager」を利用してsettings.pyで追記したDBを指定することで外部DBを参照できる。
* TablesName

```application/views.py
from django.shortcuts import render
from .models import <クラス名>


def index(request):
    data = <クラス名>.objects.db_manager("<DB名>").all()
    params = { 'data': data }
    return render(request, 'sample.html', params)
```

### マイグレーションも忘れない

```
$ python3 ./manage.py makemigrations
$ python3 ./manage.py migrate
```
Alexaスキルは、「認定済み」だが未公開の間、ベータテストは使えない

Alexaスキルを申請し、認定が通った後で、社内確認のためにベーターテスターを追加しようとしたら、以下のようになっていて、使えませんでした。審査が通る前は使えていたのですが。。。

https://developer.amazon.com/alexa/console/ask

＞スキルの「アクションを選択」で「ベータテストの管理」を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173392/2a374cb8-40a8-3270-97ea-245f9e6bed23.png)



### 認定申請を取り下げる

上記のメッセージに「スキルを審査に提出できる状態にしてから」と書いてあるので、おそるおそる＆どうせ修正箇所あるし、と思いながら「認定申請を取り下げ」します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173392/16b2ec35-4463-e12d-69f0-2c375d841d7d.png)
ワーニング画面で「はい、」の方を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173392/ecc401ef-94ed-dfe6-f20a-3c803def894c.png)


その後、βテストができるようになってました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173392/6c320862-c045-b2c1-1370-3d2af94d8c89.png)


### あるいは公開する

今回はリリース前だったので引き下げましたが、リリースしていればベータテストは使えるので、個人開発ものだったら、公開をしたでしょう。今回は会社のだったんで無理でした(^o^)

以上です。
バッチファイルで python 仮想環境を有効化後、pythonファイルを実行する

# やりたいこと
タスクスケジューラでパイソン仮想環境でのパイソンファイル実行を
自動化したかったため

# バッチファイルの中身
call と実行したいコマンドをそれぞれ&でつなぎ一行でかくことで実現可能

``` 
call xxx\Scripts\activate & cd [pythonファイルのあるパス] & python [実行したいパイソンファイル] 
```


###上記のソースでは下記を行っている
1.仮想環境の有効化
2.パイソンファイルのあるディレクトリへ移動
3.パイソンファイルの実行
【AWS】「InstanceStateとInstanceStatusは違う」という紛らわしいお話【EC2】

## 1. はじめに
タイトルだけで完結してしまっているような気もしますが、
「そうなんだ！」と思った方は少しスクロールしていただけると幸いです。

## 2. とりあえず取得してみる
早速`describe-instance-status`を叩いてみましょう。
~~と言いつつ、自分はリファレンスの実行例をコピペしましたが。~~~

```shell:describe-instance-status
$ aws ec2 describe-instance-status --instance-id i-1234567890abcdef0
{
    "InstanceStatuses": [
        {
            "InstanceId": "i-1234567890abcdef0",
            "InstanceState": {
                "Code": 16,
                "Name": "running"
            },
            "AvailabilityZone": "us-east-1d",
            "SystemStatus": {
                "Status": "ok",
                "Details": [
                    {
                        "Status": "passed",
                        "Name": "reachability"
                    }
                ]
            },
            "InstanceStatus": {
                "Status": "ok",
                "Details": [
                    {
                        "Status": "passed",
                        "Name": "reachability"
                    }
                ]
            }
        }
    ]
}
```

InstanceStateとInstanceStatusが登場しますが、StateとStatusって一緒じゃないの？ってなりますよね。
辞書を引いてみると、どちらも「状態」と訳せるようです。

## 3. 原典にあたる
`InstanceState:running`、`InstanceStatus:ok`の他にどのような値があるのか、公式から引用しました。

#### 1. InstanceState
稼働状況と考えるのがよさそうです。
>Valid Values: pending | running | shutting-down | terminated | stopping | stopped

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/APIReference/API_InstanceState.html

#### 2. InstanceStatus
ざっくりと言うと、問題の有無ということでしょうか。
>Valid Values: ok | impaired | insufficient-data | not-applicable | initializing

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/APIReference/API_InstanceStatusSummary.html

## 4. 使いどころ
例えば検証環境のインスタンスを夜間に停止し、翌朝に起動するというようなLambda関数(やシェルスクリプト)を作る場合は。。。

#### 1. State
停止/起動を実行した後に確認し、結果に応じて待機したり再度実行したりという風に使えます。

#### 2. Status
~~起動後に確認し、OKでなければ再起動するとかエラーを吐くという処理を実装しても良いかもしれません。
ただし、`ec2 wait instance-status-ok`には[注意が必要](https://dev.classmethod.jp/articles/awscli-wait-instance-status-ok-include-all-instances/)です。~~
→@netebakariさんよりコメントをいただきました。これの仕組みは自分で作らずCloudWatchにお願いしましょう。

## 5. ちなみに
[「status vs state」論争に終止符を打つ](https://qiita.com/ysn/items/01c1f849d801fab54956#回答1-1)という記事がありました。論争になるほどなんですね。
少なくともInstance{State|Status}については、以下の記載がしっくりきます。
>Status = 調子はどう？
State = 何をしているか?

## 6. おわりに
[4. 使いどころ](#4-使いどころ)で例に挙げたLambda関数を作成する際にこのあたりを詳しく調べたのですが、
だいぶ前のことなので割と忘れてしまっている部分もありました。
ナレッジはすぐに形にしないといけませんね。。。
wireshark への貢献方法

# wireshark への貢献方法

[Ubuntu 20.04 上で wireshark をビルドする](https://qiita.com/m-tmatma/items/f8c0061bcc488f126f46) の記事で書きましたが、現在 wireshark は [gitlab](https://gitlab.com/wireshark/wireshark) で管理されています。

wireshark を fork して、[Merge Request](https://gitlab.com/wireshark/wireshark/-/merge_requests) を送るだけで、wireshark に貢献することができます。

## GitLab アカウント

gitlab は Google や GitHub などのアカウントでログインできるようになっているので、これらのアカウントがあれば、gitlab の専用アカウントを作らなくても、数クリックでアカウントを作成できます。

## 貢献するための流れ

1. wireshark を `fork` して自分用のリポジトリを作る
2. wireshark に必要な修正を加えて、自分の `fork` リポジトリに push します。
3. Merge Request を送ります。
4. レビューを待ちます。
5. 必要に応じて修正します。
6. 問題がすべて解決したら、メンテナーが master にマージします。

以下は wireshark の pcap ファイルに圧縮機能を追加したものです。
https://gitlab.com/wireshark/wireshark/-/merge_requests/422

コードやコミットログが、コーディング規約にしたがっていることを求められます。
https://gitlab.com/wireshark/wireshark/-/blob/master/doc/README.developer
https://gitlab.com/wireshark/wireshark/-/wikis/Development/SubmittingPatches
three.jsで歯車回してみた

立体といえば回転。回転といえば歯車。ということで歯車回してみました。（我ながらイミフ・・）


<p class="codepen" data-height="520" data-theme-id="light" data-default-tab="result" data-user="kob58im" data-slug-hash="qBNebMP" style="height: 520px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Gears">
  <span>See the Pen <a href="https://codepen.io/kob58im/pen/qBNebMP">
  Gears</a> by kob58im (<a href="https://codepen.io/kob58im">@kob58im</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# ハマった点

`arc`の仕様がちょっとややこしい。

公式のドキュメントより抜粋：

> .arc ( x : Float, y : Float, radius : Float, startAngle : Float, endAngle : Float, clockwise : Boolean ) : this
<b>x, y -- The center of the arc offset from the last call.</b>
radius -- The radius of the arc.
startAngle -- The start angle in radians.
endAngle -- The end angle in radians.
clockwise -- Sweep the arc clockwise. Defaults to false.

直前に`arc`で移動した先の座標から、円の中心点（今回は原点(0,0)）に移動するように、パラメータ`x`,`y`を指定する必要がある。
（ドキュメントに記載されている「offset from the last call」は相対座標であることを指しているようである。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/c4554a0f-e115-dbb1-7072-7aa30b8f9cc0.png)	

```javascript:歯車の座標生成部分の抜粋（実コードから少しだけ簡略化してます）

let arcShape = new THREE.Shape();
let t = 2*Math.PI/a.tooths;
let dx1=0;
let dy1=0;
arcShape.moveTo( 0, 0 );
for(let i=0;i<a.tooths;i++) {
  let dx2 = -a.r3*Math.cos((i+a.duty)*t);
  let dy2 = -a.r3*Math.sin((i+a.duty)*t);
  arcShape.arc( dx1, dy1, a.r3, (i       )*t, (i+a.duty)*t, false );
  arcShape.arc( dx2, dy2, a.r2, (i+a.duty)*t, (i+     1)*t, false );
  dx1 = -a.r2*Math.cos((i+1)*t);
  dy1 = -a.r2*Math.sin((i+1)*t);
}
```


# 参考
 - [2次元のShapeから厚みをつけて3次元にできる](https://blog.i-o.io/?id=1343325454)
 - [Path#arc – three.js docs](https://threejs.org/docs/#api/en/extras/core/Path.arc)
LinuxでSuperColliderの音を他のアプリのマイク入力として渡す

リモートセッションでSuperCollider(以下SC)の音をブラウザに渡したい時など、SCの出力を他のアプリの入力として渡したい時の設定方法
最終的にこうする。
![qjackctl.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12251/c30dae6d-3e2f-1c81-32e5-ace351a1e734.png)

この図は`qjackctl`で表示できるグラフ。SCの出力を`PulseAudio JACK Source`に接続しているところがポイント

PulseAudioはLinuxのaudioサーバーで、ブラウザなどの最近のアプリはだいたいこれを使って音を鳴らしている。SCのようなリアルタイム性が要求されるアプリはPulseAudioではなく、JACK/ALSAを使っている。

PulseAudioのjackモジュールを使うと、jack上でPulseAudioの入出力を扱えるようになる。あと`qjackctl`(jackの設定用GUI)と`pavucontrol`(pulseaudioの設定用GUI)も入れておくと便利
arch linuxの場合は下記のコマンドで各種アプリ/モジュールをインストールできる

```
> sudo pacman -S pulseaudio-jack qjackctl pavucontrol
```

重要なポイントとして、jackを起動するタイミングでPulseAudioは停止していなければならない。`pulseaudio -k`で停止させる。
※ PulseAudioを使うアプリがあると、勝手にPulseAudioを起動してしまうので注意

```
> pulseaudio --check -v
I: [pulseaudio] main.c: Daemon running as PID 2959
> pulseaudio -k
> pulseaudio --check -v
I: [pulseaudio] main.c: Daemon not running
```
PulseAudioの停止を確認したら、下記の手順で設定する。

1. `qjackctl`の「開始」ボタンを押して、jackを起動する

2. `PulseAudio`のjackモジュールをロードする

    ```
> pactl load-module module-jack-sink channels=2
> pactl load-module module-jack-source
```

3. SuperColliderを起動する

4. `PulseAudio`を使うアプリを起動して、マイク入力を使う状態にする
    `pavucontrol`の「録音」タブから起動したアプリが`Jack source`と接続されていることを確認できる
![pavucontrol.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12251/10d6775a-3683-3f92-1a27-9bad05d997ca.png)

5. qjackctlの「グラフ」でこのページの最初の図のように`PulseAudio JACK Source`に接続する
AWS AthenaでWAFで検知したDoS攻撃の内容を調査する

## 目的
AWS WAFで設定した、DoS攻撃検知用のルールが稼働していると思われるログがあったので、DoS攻撃の詳細内容を調査した時の方法をまとめておくものです。

## 前提となる環境
以下のサービスが有効になっている環境での調査方法となります。
※ この環境ではWAF自体のロギングを有効としていないため、ALBのログを調査します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/dd175e42-076d-b5fe-68c8-1618e7d6861e.png" width=70%>

### AWS WAF
 * AWS WAFにて、ACL(アクセスコントロールリスト)として5分辺りのリクエスト件数を低閾値としたrate limit baseのルール設定が可能です。該当の環境では2000件を設定しており、これを越した時をDoS攻撃とみなしています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/4646dd48-2b33-89d0-1ced-e6518fab470d.png" width=70%>

### AWS ALB
  * 有効にしたWAFはALBに割り当てます。
  * Athenaで検索するために、ALBのログをs3に保管しておく必要があります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/ff0589a0-53f2-7b3e-ee25-c7b0b489dbf8.png" width=80%>

なお、対象となるs3のオブジェクトを特定することが出来れば、Athenaを利用しなくてもs3単体でクエリを実行することも可能です。

### AWS Glue
  * AWS Athenaは AWS Glueのデータカタログを使用してS3に格納されたデータに接続し、テーブルや列名などのメタデータを格納できます。そのため、あらかじめAWS GlueにALBのログを保管したs3を指定してのデータベースを作成しておく必要があります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/4a845a7c-82b7-e566-351f-7b07b8cc1d58.png" width=80%>

         
### AWS Athena
  * 上記3サービスの準備が出来ればAthenaを使った分析が可能になります。

## 調査

### CloudWatch
WAFのメトリクスを指定します。
DoS攻撃検知用のルールに合致している日時を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/e4a223f9-409b-f831-974a-57b92837fbe4.png)

### AWS Athena
最初にクエリを実行するためのデータソースを指定します。
Glueで定義したALBのデータベースが選択できるので、対象のデータベースを選択するとALBのログを保管したs3テーブルが表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/0ebf4ab7-8d5a-62a9-fcee-b9fee41fa5a0.png" width=50%>

DoS攻撃検知用のルールが稼働していた日時をfrom~toで指定し、結果を保存するためのs3を指定してクエリを実行します。
クエリの内容は今回は以下のような内容です。

```
SELECT *
FROM alb_web_logs
WHERE parse_datetime(time,'yyyy-MM-dd''T''HH:mm:ss.SSSSSS''Z') 
     BETWEEN parse_datetime('2020-xx-xx-x:00:00','yyyy-MM-dd-HH:mm:ss') 
     AND parse_datetime('2020-yy-yy-y:00:00','yyyy-MM-dd-HH:mm:ss')
ORDER BY received_bytes DESC
```

クエリが成功すると、実行結果は管理画面でも表示されます。
結果はcsvでも出力可能です。

今回ALBログの項目で注目するのは`actions_executed`項目です。
値が`waf,forward`であればALBでリクエストをターゲットに転送したという内容となりますが、
WAFのDoS攻撃検知用のルールに合致してリクエストを拒否する必要があると判別した場合、以下のように`waf`という値になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/8dfaa39e-dbc7-8d19-a4ca-905c1805262b.png)

`waf`となった該当ログのうち、主に以下の項目を確認して攻撃内容を把握します。

|項目名  |確認内容  |
|---|---|
|elb  |対象となったALB  |
|request_url  |対象となったURL  |
|client_ip  |アクセス元のIPアドレス  |
|client_port  |利用ポート  |

全項目の内容は以下の通りです。
[【公式ドキュメント】Application Load Balancer のアクセスログ]
(https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/load-balancer-access-logs.html)

## 結果
ALBログからWAFの検知した時の状況を確認することが出来ました。
対応としてはAWS WAFでActionを`Count`に設定しておくと検知した内容をログとして残しておくだけですが、`Block`に設定しておくと、 HTTPステータス403(Forbidden)を返します。<br>
`Block`にする場合、rate limit baseのルールが正当なアクセスをblockすることのないように閾値を調整する必要があります。

## 参考

[Application Load Balancer のアクセスログから WAF による遮断を確認する方法](https://www.wafcharm.com/blog/checking-waf-block-from-alb-access-log/#step5)
【SQS】種類 / メッセージ取得方法 / その他機能

## キューの種類
### 1. スタンダードキュー
- 受信側がキューに対してメッセージをリクエストした場合、配信は少なくとも1回行われる
- リクエストのタイミングによってはメッセージが複数回配信される場合がある
- キューに対してメッセージが送信された順序とは異なる順序で配信される可能性がある（**ベストエフォート型**）


### 2. FIFOキュー
- 受信側がキューに対してメッセージをリクエストした場合、1回限りで確実に処理される
- キューに対してメッセージが送信された順序で配信される（**First-In-First-Out**）
- 単一のキュー内に複数のメッセージグループを持ち、メッセージグループ単位で順序性を担保する機能を有する
- リクエストあたりの料金がスタンダードキューよりも高い

## メッセージの取得方法
### 1. ショートポーリング
キュー内のメッセージの有無に関わらず、キューに対してメッセージの取得要求を送信する方法

- デフォルトのメッセージ取得方法
- キュー内にメッセージが存在しない場合、「Empty」という空のレスポンスを返す
- 空のレスポンスでもリクエスト回数に応じて料金が発生するため、メッセージの発生頻度によってはコスト効率が悪くなる

### 2. ロングポーリング
メッセージ取得までの待ち時間を設定し空のレスポンス数を削減することで、SQSの使用コストを削減する方法

- Receive MessageのWaitTimeSecondsを0秒以上に設定するとロングポーリングとなる
- メッセージ取得までの待ち時間は **0秒から20秒** の間で設定可能
- キュー内にメッセージが無い場合、最大Receive MessageのWaitTimeSecondsで指定した時間だけキューの取得処理を待機する

## 遅延キュー
キューへの新しいメッセージの配信を数秒間遅延させることが出来る機能

- キューに送信したメッセージは、指定した遅延時間受信側に表示されない
- キュー全体に対する遅延時間は遅延キュー内で指定、個々のメッセージの遅延時間はメッセージタイマーで指定
<br>→ **デフォルト遅延は0秒、最大15分**

## 可視性タイムアウト
メッセージを取得してから一定時間、同一メッセージを他のシステムから取得出来ないようブロックする機能

- ブロックする時間は **デフォルト設定で30秒**
- メッセージを受信したシステムがタイムアウト時間内に処理を完了し、SQS内のキューのメッセージを削除することで、同一メッセージによる処理が複数回実行されるのを防止することが可能

## デッドレターキュー
処理失敗時に、何回かリトライを実施した後、自動的に別のキューにメッセージを移動する機能

- 処理に失敗したメッセージの識別が可能
- 当該メッセージがキュー内に残り続けることを防止する
【SQS】SQSとは

## SQSとは
**Amazon Simple Queue Service** の略
システムやプロセスの間でデータを非同期でやりとりするメッセージキューサービス

- トラフィック量を想定出来ないようなメッセージをSQSのキューにバッファリングし、そのメッセージを後から処理するようなシステムで利用される
- オートスケールで運用負荷を軽減する目的でも利用される

## 特徴
- 受信側がキューに問い合わせてメッセージを取得するPull型のサービス
- 送信側・受信側の増加によってメッセージが増加すると。自動的にスケールアップし高スループットを実現する
- キューに格納出来る **1メッセージあたりのサイズは最大256KB**
<br>→ Extended Client Library を利用することで最大2GBに設定可能
- メッセージの送信・受信は **1APIリクエストあたり最大10件**
- キューのメッセージの **保存期間はデフォルトで4日間**
<br>→ 60秒から14日間の間で保存期間を設定可能

## メッセージの識別子
SQLで利用出来る識別子は以下3種類

**1. キューURL**：キュー作成時に生成されるURL（`https://sqs.ap-northeast-1.amazonaws.com/[AWSアカウント番号]/[キューの名前]`）
→ キューに対してアクションを実行する際に指定

**2. メッセージID**：SQSが受信したメッセージに対して割り当てるID
→ メッセージの特定に使用

**3. 受信ハンドル**：キューからメッセージを受信する都度発行される最大長1,024文字の文字列
→ キューにあるメッセージの削除や可視性の変更を要求する際に指定
Rails wheneverをdockerで実行する

# 動機

Railsの定期実行を行う`whenever`を使おうとしたのですが、Mac上で直接実装すると環境変数や権限管理で大変だったのでdockerで行うことにしました

# 前提条件

実行環境は下記のようになります
・ ruby 2.6.3
・ Rails 5.2.4
・ MySQL ８.0.19　

#

# Docker関係

`Dockfile`、`docker-compose.yml`は以下のようになります
通常のdockerの設定とあまり変わらないのですが、今回はwheneverを利用するので`corn`のインストールとcronをフォアグラウンド実行するための設定を追記しています。


```Dockfile
FROM ruby:2.6.3
#rubyのバージョン指定

#gemのインストール
RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs

# cronインストール
RUN apt-get install -y cron 

RUN mkdir /my_app

WORKDIR /my_app

COPY Gemfile /my_app/Gemfile
COPY Gemfile.lock /my_app/Gemfile.lock

RUN gem install bundler
RUN bundle install

COPY . /my_app

# wheneverでcrontab書き込み
RUN bundle exec whenever --update-crontab 

# cronをフォアグラウンド実行
CMD ["cron", "-f"] 

```

```docker-compose.yml
version: '2'
services:
  db:
    image: mysql:8.0.19
    command: 
      --default-authentication-plugin=mysql_native_password
    volumes:
      - ./mysql-confd:/etc/mysql/conf.d
      - mysql-data:/var/lib/mysql    #データの永続化のため
    ports:
      - "3306:3306"
    restart: always
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 1
      # MYSQL_DATABASE: app_development
      MYSQL_USER: root
      # MYSQL_PASSWORD: password
      TZ: Asia/Tokyo
  app:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/my_app   
      - bundle:/usr/local/bundle   
    ports:
      - "3000:3000"
    links:
      - db

volumes:
  mysql-data:
  bundle:      #bundle installした後buildし直さなくてよくなる

```

# whenever関係

```config/schedule.rb
# wheneverにrailsを起動する必要があるためRails.rootを使用
require File.expand_path(File.dirname(__FILE__) + "/environment")

# 環境変数をうまい感じにやってくれる
ENV.each { |k, v| env(k, v) }

# ログを書き出すようファイル
set :output, error: 'log/crontab_error.log', standard: 'log/crontab.log'
set :environment, :development

#2分毎に`sample_task`の`scheduled_task`を実行する
every 2.minutes do
  rake 'sample_task:scheduled_task'
  # runner "Test.yakisoba", :environment => :development # runnnerの例
end
```

# 実行したいコマンド

```/lib/tasks/sample_task.rb
namespace :sample_task
  desc "scheduled_task"
  task scheduled_task: :environment do
     ..... 実行したい関数
    end
  end
end
```
PythonでオプションをJSONファイルとコマンドライン引数の双方から取得する

別記事、[RubyでオプションをJSONファイルとコマンドライン引数の双方から取得する](https://qiita.com/aikige/items/014139c0b1ae70139477) の Python 版です。

JSONと、コマンドラインオプションの双方から設定を読み込むためのクラスを作成してみます。

## 基本的なコンセプト

1. オプションは一つのオブジェクトに集める。  
   例: `class Options`
2. オブジェクト生成時に、JSONファイルからオプションを読み込む。  
   例: `opt = Options('some.json')` とするとJSONファイルからオプションを読み込む。
3. オプションにアクセスするときはオブジェクトの attribute を使う。  
   例: `opt.option_1` のような感じでアクセスできる。
4. コマンドライン引数は [Argparse](https://docs.python.org/ja/3/howto/argparse.html) を使って一次処理を行う。
5. オプションのオブジェクトは、Arguparse の生成した結果から値を取り込むメソッドを持つ。  
  例: `opt.import_opt(options)` のような感じでインポートする。

## 実装例

https://gist.github.com/aikige/470f4ef93753638cc3a18d62e195eb19

```python
#!/usr/bin/env python

import json
import os

TEMPLATE_SAMPLE = { 'option_1': 'bool', 'option_2': 'str', 'option_3': 'int' }

class Options:
    def __init__(self,
            filename='config.json',
            template = TEMPLATE_SAMPLE):
        self.template = template
        if (os.path.exists(filename)):
            with open(filename) as f:
                self.__import_dict(json.load(f))

    def __import_attr(self, key, value):
        if (value is None):
            return
        if (isinstance(value, eval(self.template[key]))):
            setattr(self, key, value)
        else:
            raise ValueError("invalid type")

    def __import_dict(self, d):
        for key in self.template.keys():
            self.__import_attr(key, d.get(key))

    def import_opt(self, o):
        for key in self.template.keys():
            if (hasattr(o, key)):
                self.__import_attr(key, getattr(o, key))

if __name__ == '__main__':
    import argparse

    opt = Options()
    print(vars(opt))

    parser = argparse.ArgumentParser()
    parser.add_argument('-1', '--option_1', type=bool) 
    parser.add_argument('-2', '--option_2', type=str)
    parser.add_argument('-3', '--option_3', type=int)
    args = parser.parse_args()
    print(vars(args))

    opt.import_opt(args)
    print(vars(opt))
```

## 補足

* 定数 `OPTS` がオプションのデータ取り込みのためのテンプレートになっていて、名前と型の組を格納している。

## [Ruby版](https://qiita.com/aikige/items/014139c0b1ae70139477) との主な違い

1. Ruby と違って Python には `bool` 型があるので、型判定を比較的簡単に組み込める（`__import_attr`）。
2. Python の場合、オプション解析に [Arrgparse](https://docs.python.org/ja/3/howto/argparse.html) を使うので、JSON解析と引数解析のメソッドが統一できておらず、若干冗長になる。
3. Python の場合、attribute を読み取り専用にするのが少し難しそうなので、省略している。
Linuxでnologinユーザーをログインできるようにする方法

Linuxでnologinユーザーをログイン出来るようにしたり、ログインユーザーをnologinユーザーに変更する検証をしたのでまとめました。

### 検証環境
- AWS
- Ubuntu20.04

### 検証内容
#### 1.デフォルトだと`www-data`ユーザーが`nologin`の設定になっているので、このユーザーで検証する
```
$ cat /etc/passwd | grep www-data
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
```

#### 2.`/var/www` ディレクトリが存在していないので作成する
```
$ sudo mkdir -p /var/www
$ sudo chown www-data:www-data /var/www
$ ls -alF /var/www
total 8
drwxr-xr-x  2 www-data www-data 4096 Nov  7 05:34 ./
drwxr-xr-x 14 root     root     4096 Nov  7 05:34 ../
```

#### 3.設定変更しないで、nologinユーザーにログインする
```
$ sudo su - www-data --shell=/bin/bash
```

#### 4.デフォルトで作成されている `www-data` ユーザーをログインできるよう設定する
```
### ログインできないことを確認
$ sudo su - www-data
This account is currently not available.

### ログインできるように設定
$ sudo usermod -s /bin/bash www-data
$ cat /etc/passwd | grep www-data
www-data:x:33:33:www-data:/var/www:/bin/bash
```

#### 5.ログインユーザーからnologinユーザーに設定変更
```
### ログインできないように設定
$ sudo usermod -s /usr/sbin/nologin www-data
$  cat /etc/passwd | grep www-data
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

### ログインできないことを確認
$ sudo su - www-data
This account is currently not available.
```

### 参考
- [まかあそてっく](https://makaaso-tech.com/linux-nologin-setup/)
【RSpec】Ruby on Rails チュートリアル「第３章」のテストを RSpec で書いてみた

# はじめに
Ruby on Rails チュートリアルでは「minitest」を使用してテストが実施されていますが、実際の現場では主に「RSpec」を使用してテストを実施するとのことなので、チュートリアルのテストを実際の現場に近づけるために「RSpec」で実施するようにしました。まず手初めに「第３章」のテストを RSpec で書き換えてみたので、同じようにチュートリアルのテストを RSpec で実施してみたい人の参考になれば幸いです。
## 対象者
+ Ruby on Rails チュートリアルのテストを Rspec で実施予定、または実施してみたい人
+ Ruby on Rails チュートリアル「第3章」のテストを Rspec で書きたいけど、書き方が分からない人

## テストコード
実際にテストコードを書き換えた結果が下記になります。
### Minitest
```ruby:static_pages_controller_test.rb
require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  def setup
    @base_title = "Ruby on Rails Tutorial Sample App"
  end

  test "should get home" do
    get static_pages_home_url
    assert_response :success
    assert_select "title", "Home | #{@base_title}"
  end

  test "should get help" do
    get static_pages_help_url
    assert_response :success
    assert_select "title", "Help | #{@base_title}"
  end

  test "should get about" do
    get static_pages_about_url
    assert_response :success
    assert_select "title", "About | #{@base_title}"
  end
end
```
### RSpec
```static_pages_spec.rb
require "rails_helper"

RSpec.describe "StaticPages", type: :request do
  before do
    @base_title = "Ruby on Rails Tutorial Sample App"
  end

  describe "Home page" do
    it "should get home" do
      visit static_pages_home_url # /static_pages/home/ へアクセス
      expect(page.status_code).to eq(200) # HTTP ステータスコードが "200" か判定
      expect(page).to have_title "Home | #{@base_title}" # タイトルに 「Home | Ruby on Rails Tutorial Sample App」 が含まれているか判定
    end
  end

  describe "Help page" do
    it "should get help" do
      visit static_pages_help_url
      expect(page.status_code).to eq(200)
      expect(page).to have_title "Help | #{@base_title}"
    end
  end

  describe "About page" do
    it "should get about" do
      visit static_pages_about_url
      expect(page.status_code).to eq(200)
      expect(page).to have_title "About | #{@base_title}"
    end
  end
end
```
##次回
「第4章」のテストコードを RSpec に書き換える予定です。
Grove - Temperature&Humidity Sensor(DHT11)をRaspberry Piで使用する

# はじめに

HAT基盤と温度センサーを買ったのはよかったけど、動かしていなかったので、動作検証代わりに行った。

お値段等
* [千石電商 Grove Base Hat for Raspberry Pi](https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-5FAG)  ¥1304
* [Grove - デジタル温度・湿度センサー] (https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-4ENZ) ¥662

# Grove Base Hat for Raspberry Piの準備
[Seeed Studioの商品ページ](https://wiki.seeedstudio.com/Grove_Base_Hat_for_Raspberry_Pi/)を参考に
以下のコマンドを入力してセットアップをする。

```bash
curl -sL https://github.com/Seeed-Studio/grove.py/raw/master/install.sh | sudo bash -s -
```
実行しばらく待つと以下のメッセージが表示される。

```bash
Successfully installed grove.py-0.6
#######################################################
  Lastest Grove.py from github install complete   !!!!!
#######################################################
```

# Grove - Temperature&Humidity Sensor(DHT11)を動作する。

[Seeed Studioの商品ページ](https://wiki.seeedstudio.com/Grove-TemperatureAndHumidity_Sensor/)を参考にして、
センサーをHat基盤のPWMに接続をする。

以下のコマンドでライブラリをインストールする。

```
git clone https://github.com/Seeed-Studio/Seeed_Python_DHT.git
cd Seeed_Python_DHT
sudo python setup.py install
```
インストールしたら以下の通りメッセージが表示される。

```
Installed /usr/local/lib/python2.7/dist-packages/seeed_python_dht-0.0.1-py2.7.egg
Processing dependencies for seeed-python-dht==0.0.1
Finished processing dependencies for seeed-python-dht==0.0.1
```

サンプルコードがgitからコピーしたフォルダ内にあるので、実行する

```
python examples/dht_simpleread.py
```

実行すると以下の通り温度と湿度を返す。

```
DHT11, humidity 66.0%, temperature 23.0*
DHT11, humidity 68.0%, temperature 21.0*
DHT11, humidity 74.0%, temperature 21.0*
DHT11, humidity 74.0%, temperature 21.0*
```

# 余談
goで温度を測定するプログラムは以下のサイトを参考にする
https://github.com/d2r2/go-dht

example1のサンプルコードで以下の修正を加えて

```go
sensorType := dht.DHT11
pin := 12
```

rootユーザで実行すれば温度・湿度測定ができる。
Udemy フルスタック・Webエンジニア講座　セクション4.Javascript

<html lang="ja">
<body>
<h1>きれいに整える</h1>
インテンドを活用してコードをきれいにする。
後々見なすときに便利！使いやすさを大事にしよう。

<h1>id,class</h1>
個別属性はid、共通属性はclassで指定すること。

<h1>getElementByIdの使い方</h1>
 **getElementById**というメソッドは、idプロパティが指定された文字列に一致する要素を表すElementオブジェクトを返すことが出来る。

例)

```javascript:test.js
document.getElementById("buttom").onclick = function(){
         alert("○○");
```
ここでbuttomを押すと、alertで"○○"が出てくる。

<h1>if文、for文</h1>
if文

```javascript:test.js
if (条件) {
条件が真であれば実行
} else {
条件が偽であれば実行
}
```

for文

```javascript:test.js
 for (let i = 0; i < 3; i++) {
   console.log("ただいま" + (i+1) + "回目。");
 }
```
実行すると、

ただいま1回目
ただいま2回目
ただいま3回目

となる。

<h1>よく出たエラー</h1>
スペルミスを多くしてしまい、エラーがでることが多々あった。前述したとおり、インテンドなどして、コード綺麗にしておけば、すぐエラーを修正することが出来た。

<h1>最後に</h1>
洗練さんでインターン1日目(フルスタック・Webエンジニア講座　セクション4.Javascript)です。
[フルスタック・Webエンジニア講座](https://www.udemy.com/course/completeweb2_jp/learn/lecture/4969902#overview)
Javascriptを初めて学びましたが、この講座ではJavascriptの基礎を分かりやすく解説してくださるので、プログラミング初学者にはおすすめの講座です。

最後に
現在ここでインターンしています。
まだまだ駆け出しですが頑張ります！やる気は人一倍です！
https://senren.work/

</body>
</html>
【PHP/Laravel】php artisan serveを使わずにVirtual Hostを設定する

#【PHP/Laravel】php artisan serveを使わずにVirtual Hostを設定する


###使用環境

* windows 10 Home(COREi7)
* XAMPP 7.3.18
* Laravel 6

##そもそもVirtual Hostって何？
>バーチャルホストという用語は、1 台のマシン上で (company1.com と company2.com のような) 二つ以上のウェブサイトを扱う運用方法のことを指します。
[Apache バーチャルホスト説明書](https://httpd.apache.org/docs/2.4/ja/vhosts/)

##virtual hostの設定
* ```C:\xampp\apache\conf\extra``` の中の```httpd-vhost.conf```ファイルをまずコピーを取っておく。

* 以下の部分を書き換える 


```
##NameVirtualHost *:80

・・・中略・・・

##<VirtualHost *:80>
    ##ServerAdmin webmaster@dummy-host.example.com
    ##DocumentRoot "C:/xampp/htdocs/dummy-host.example.com"
    ##ServerName dummy-host.example.com
    ##ServerAlias www.dummy-host.example.com
    ##ErrorLog "logs/dummy-host.example.com-error.log"
    ##CustomLog "logs/dummy-host.example.com-access.log" common
##</VirtualHost>

```

* 下記を参考に書き換える

```
NameVirtualHost *:80

・・・中略・・・

Listen 10001
<VirtualHost localhost:10001>
    ##ServerAdmin webmaster@dummy-host.example.com
    DocumentRoot "C:\laravel\practice\public"
    ##ServerName dummy-host.example.com
    ##ServerAlias www.dummy-host.example.com
    ##ErrorLog "logs/dummy-host.example.com-error.log"
    ##CustomLog "logs/dummy-host.example.com-access.log" common
    <Directory "C:\laravel\practice\public">
      AllowOverride All
      Options +Indexes
      Require all granted
    </Directory>
</VirtualHost>


```


* 新しくポートを作る。

* 任意で指定するポート番号を付ける(今回は10001を使用)
Listen 10001と記入

* その下にVirtualHostの設定を書き込む
DocumentRootの#を外しディレクトリ設定を書き込む
　AllowOverride All
　→.htaccessで設定可能なものは全て有効になる

　Options +Indexes
　→ディレクトリに対するリクエストに対して、DirectoryIndex で指定したファイル（index.html 等）が存在しない場合に、ディレクトリ内ファイルの一覧を表示

　Require all granted
　→無条件でアクセスを許可

[Apache HTTP SERVER PROJECT](https://httpd.apache.org/docs/2.2/ja/howto/htaccess.html)


* DocumentRootとDirectoryにLaravelプロジェクト(今回はpractice)のpublicを指定する



###参考
[Qiita:ApacheのVirtual Hostってなんだ](https://qiita.com/monaky/items/b5f589e59ee002a1d355)
【Nuxt.js】Vuex クラシックモードとモジュールモードの書き方【コピペ用】

#クラシックモード
```javascript:store/index.js
import Vuex from "vuex"

const createStore = () => {
  return new Vuex.Store({
    state() {
      return {
        count: 0
      };
    },
    getters: {
      getCount: state => state.count
    },
    mutations: {
      incrementCountMutations(state) {
        state.count++;
      }
    },
    actions: {
      incrementCountActions({ commit }) {
        commit("incrementCountMutations");
      }
    }
  });
};

export default createStore

```
#モジュールモード

**動作条件**
・index.jsがstoreオブジェクトをexportしない
・store配下にindex.jsが存在しない

store配下にcounter.jsを作成

```javascript:store/counter.js
export const state = () => ({
  count: 0
})

export const getters = {
  getCount: state => state.count
}

export const mutations = {
  incrementCountMutations(state) {
    state.count++;
  }
}

export const actions = {
  incrementCountActions({ commit }) {
    commit("incrementCountMutations");
  }
}

```

# 呼び出す（counter.jsの場合）
```vuejs:pages/index.vue
<template>
  <div>
    state : {{ $store.state.counter.count }}
    <br>
    getters : {{ $store.getters['counter/getCount'] }}
    <br>
    <button @click="$store.commit('counter/incrementCountMutations')">incrementCountMutations</button>
    <br>
    <button @click="$store.dispatch('counter/incrementCountActions')">incrementCountActions</button>
  </div>
</template>
```
#ヘルパー関数を使用する

```vuejs:pages/index.vue
<template>
  <div>
    state : {{ count }}
    <br>
    getters : {{ getCount }}
    <br>
    <button @click="incrementCountMutations">incrementCountMutations</button>
    <br>
    <button @click="incrementCountActions">incrementCountActions</button>
  </div>
</template>

<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
  computed: {
    ...mapState('counter', [
      'count'
    ]),
    ...mapGetters('counter', [
      'getCount'
    ])
  },
  methods: {
    ...mapMutations('counter', [
      'incrementCountMutations'
    ]),
    ...mapActions('counter', [
      'incrementCountActions'
    ])
  }
}
</script>
```
No Code でFX自動売買を作成する

#自動売買を作るには
##初めに
この記事ではNo CodeでのFX自動売買（以下EAと記述します）の作成方法を書きます。
　ちなみに本記事で紹介する方法で作成したEAのバックテスト結果です。
![スクリーンショット 2020-11-23 182707.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/873389/1c1b48bf-3983-97d4-820d-1949ad03894b.png)



こんな感じで自分のロジックが簡単にEA化できちゃいます！
今回は、
<dt>100万円スタート</dt><dt>0.01Lot</dt><dt>期間2017/7/15~2020/7/15</dt>
上記条件の運用の結果です。コロナ相場をしっかりと乗り越えられています！
黒塗りの部分はロジックに関係するのでお見せできません。

##No code でEAを作成する
EAには一般的にMQL言語、もしくはPythonが使われます。（これ以外の言語があったらコメントで教えてほしいです。）
「プログラミングを勉強するのは大変だし、時間がかかる！」
と思う方がいらっしゃると思います。
ご心配なく。実は**プログラミングできなくてもEAが作れる方法があります！**

方法としては以下の3つがあります
1. [EAつくーる](https://www.gogojungle.co.jp/tools/ebooks/5735)を購入して作成する。
2. [Let's Real](https://www.lets-real.com/index.php?route=common/home)のサイトで**無料で**作成する。
3. EA作成代行に依頼する。

実際にはもっと方法はあるかもしれませんが知ってるのはこれぐらいです。

どのパターンも使い方は手順書がそれぞれのサイトにPDFで保存されているのでそちらを読んだほうが分かりやすいと思います。そして自分のロジックを持っている前提になります。
例えばRSIの数値が90を超えたら逆張り．．などです。

##最後に
今回は軽くNoCodeでのEAの作り方をご紹介しました。必要に応じて更新していきたいと思います。
作成したEAはしっかりバックテストを取った上で運用しましょう。
今回はこの辺で失礼します！
【2020年11月版】Kubernetesをk3d+Lensで超速で立ち上げる

# k3d を使ってみました。

k3d = k3s on docker。つまり docker上でk3sをうごかすものです。
そして・・・、簡単です。とっても。

## 前提条件

dockerは動かしておいてください。

# 1. k3d のインストール＆クラスタの作成

## 1-1. インストール

以下のコマンドでk3dをインストールします。

```shell
$ curl -s https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash
```

## 1-2. クラスタの立ち上げ

インストール後、以下のコマンドでクラスタを作成します。

```shell
$ k3d cluster create test-cluster
```
この`test-cluster`は作成するクラスタの名前です。好きなお名前でどうぞ。

## 1-3. クラスタに接続

以下のコマンドで`kubectl`の接続先を上記で作成したクラスタに指定します。

```
$ k3d kubeconfig merge test-cluster --switch-context
```
以上で、`kubectl` が使えるようになっております。

# 2. Lensのインストールと立ち上げ

以下の公式サイトからgithubサイトへのリンクがありますのでバイナリがダウンロード可能です。

- [Lens Github v3.6.9リリース](https://github.com/lensapp/lens/releases/tag/v3.6.9)

インストール後、lensを立ち上げると・・・

![スクリーンショット 2020-11-23 19.48.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/a573ca8a-b612-dff8-e598-6b37c030fbf2.png)

左上のクラスタ追加ボタンがありますので、クリックすると・・・

![スクリーンショット-2020-11-23-19-49-58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/faea5b44-ee99-ef07-37df-9252b28fbf1f.png)

はい、上記で作成したk3d-test-clusterへの接続準備、オーライでございます。
`Add cluster(s)`をクリックすると・・・

![スクリーンショット 2020-11-23 19.55.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/abc49988-50ea-e136-5ad3-cc223a447a3f.png)

はい、このような感じでクラスタに接続完了です。このままLensからいろいろとクラスタの操作が行えてしまいます。

だいたい5分くらい？ですよ。。。

# クラスタの削除

以下のコマンドでクラスタの削除です。

```shell
$ k3d cluster delete test-cluster
```
k3d、めっちゃお手軽やん。。。

以上です！！

# 参考サイト

- [k3d プロジェクト公式サイト](https://k3d.io/)
- [k3d Githubリポジトリ](https://github.com/rancher/k3d)
- [k3s Rancher 公式サイト](https://rancher.com/docs/k3s/latest/en/)
- [Lens 公式サイト](https://k8slens.dev/)
- [Lens githubリポジトリ](https://github.com/lensapp/lens/)