python マルチプロセスでプロセスの終了を検出する
やりたいこと
各プロセスに永続する処理を実行させた際に、1つでも例外で終了したら他すべてのプロセスを終了させ、プログラム全体を終了させたい。
コード
import multiprocessing import time def worker(n): while True: time.sleep(n) if n==2: raise Exception("error") if __name__ == '__main__': processes = [] # 3つのプロセスを起動 p1 = multiprocessing.Process(target=worker, args=(1,)) p2 = multiprocessing.Process(target=worker, args=(2,)) p3 = multiprocessing.Process(target=worker, args=(3,)) p1.start() p2.start() p3.start() processes.append(p1) processes.append(p2) processes.append(p3) # どれかのプロセスが終了するまで待機 while all([p.is_alive() for p in processes]): time.sleep(1) continue # すべてのプロセスを終了させる for p in processes: p.terminate() p.join()
出力 2秒後に例外が発生し、プログラム全体終了します。
Process Process-2: Traceback (most recent call last): File "/usr/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap self.run() File "/usr/lib/python3.8/multiprocessing/process.py", line 108, in run self._target(*self._args, **self._kwargs) File "/home/wood_stock/work/tmp/multi_prosecc_exc.py", line 8, in worker raise Exception("error") Exception: error
LINE Bot で買い物リストを管理
きっかけ
お買い物の際、家に帰りついた時に「あ、、あれ買ってなかった、、、」となることがよくあります。1度出かけた後、その買い物だけのために外出するのは腰が上がらないので、次の機会にと思うのですが次も必ず買い忘れます。 これを防止するためメモアプリやTODOアプリをダウンロードし買い物リストを作成する事をやってきたのですが、そのアプリ自体を開く事を忘れるので、結果買い忘れます、、、。
そこでLINEであれば必ず起動するので、買い物リストが目に入る事も増え、買い忘れることはないのではないかと思いました。 買い物リストを管理できそうなbotを探してみたのですが見つからなかったので、今回LINE Botを作成してみました。
やりたいこと
- ラインのトーク画面で、買い物リストの追加・編集する
- 家族などと買い物リストを共有する
作ったLINEBot
(未完成な部分や、不完全な動作がある可能性はありますので、重要なデータ等の追加はご遠慮ください。)
構成
- LINE Message API
- WebHook
- 自動応答メッセージ
- FireBase
- Functions (WebHookの処理、買い物リスト編集、メッセージ返信)
- FireStore (買い物リストデータ保存)
作成手順
1. LINE Bot の作成
Botを作成するために、プロバイダーとチャネルを手順を参考に作成しました。
Messaging APIを始めよう | LINE Developers
2.自動応答メッセージ作成
送信されたメッセージ毎に返信する定型文を設定することができたので、使い方確認に使用しました。
「使い方」と送信された場合の応答メッセージを設定しています。
3. FireBase プロジェクト作成、環境作成、デプロイ
こちらのドキュメントを参考に、プロジェクト作成から一旦動作する関数をデプロイしました。firestore のエミュレータを使用すればローカルでもfunctionsとfirestoreが動作し、firestoreへのデータの追加などが本番環境のように行えたので開発がやりやすかったです。
はじめに: 最初の関数の記述、テスト、デプロイ | Cloud Functions for Firebase
4. functionsでの処理作成
functionsでLINE MeeageAPIのwebhookを受け取り、リスト追加などの処理を追加しました。
LINE MessageAPIのwebhookは、発生したイベントごとにイベントタイプが割り当てられています。今回は、友達追加時のfollow
イベントと、メッセージ受信時のmessage
イベントを使用しています。
イベント毎に受信するメッセージは、こちらのAPIリファレンスから確認できます。
メッセージ(Webhook)を受信する | LINE Developers
// 関数の作成 export const lineWebHook = functions .https .onRequest(async (req, res) => { if (req.method == "POST") { const reqBody = req.body; const reqEvents = reqBody.events; await eventHandle(reqEvents); } res.json({result: "OK"}); }); // 発生したイベント毎の処理 const eventHandle = async (events: any[]) => { events.forEach(async (event) => { switch (event.type) { case "follow": // 友達追加された時の処理 follow(event); break; case "message": // メッセージを受信した時の処理 message(event); break; } }); };
実際のトーク画面
追加
複数項目をまとめて追加出来るよう、改行に対応しています。
リスト確認
各項目に連番を振って管理しています。
購入完了
割り当てられた番号を入力することで購入完了とし、リストから除外しています。
こちらも複数まとめて購入完了と出来るよう、改行に対応しています。
やってみたいこと
- 購入されない項目のリマインド機能
- functionsの定期実行で、一定期間購入されていない項目があれば通知する?
- メッセージの応答ではなく配信は、無料プランの場合200通までしか送れない
- 共有リストの場合、他のメンバーが追加した項目がある場合に通知
- リッチメッセージで、購入完了処理を1タップで行えるようにする
vite+Vue3 単体テストを書いてみる
WebアプリケーションのフロントエンドフレームワークとしてVue3をよく使用しています。
ただ、テストコードまでは作成できておらず、コードの品質を担保することが出来ていませんでした。
今回は、公式の Vue Test Utils にあるチュートリアルを実践してみます。
テストコードを書くより、テストを走らせるまでの準備が大変でした。
A Crash Course | Vue Test Utils (vuejs.org)
実行環境
$ node -v v18.14.0 $ npm --version 9.4.2
プロジェクト作成
vite を使用し、Vue3のプロジェクトを作成します。
# プロジェクト作成 $ npm create vite@latest ✔ Project name: … vue-test-project ✔ Select a framework: › Vue ✔ Select a variant: › JavaScript # 開発サーバー起動 $ cd vue-test-project $ npm install $ npm run dev > vue-test-project@0.0.0 dev > vite VITE v4.1.2 ready in 185 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h to show help
起動し、指定されたURLにアクセスします。
テスト用ライブラリの追加
Vueコンポーネントテスト
まずは、公式のVue Test Utils を参考にインストールします。
Installation | Vue Test Utils (vuejs.org)
$ npm install --save-dev @vue/test-utils
テストランナーで推奨されているjest
をインストール
次にインストールするvue-jest
に合わせるため、バージョンを固定します。
$ npm install --save-dev jest@28.1.3
jest
でVueコンポーネント(.vue)をテストする場合、vue-jest
というtransformerが必要という事でした。調べてもわからなかったのですが、おそらくテスト用にファイルを変換してくれるものだと思います。
こちらはVue
とjest
のバージョンによって、パッケージとバージョンが決まっているようでした。今回はVue3 , jest@28.x なので@vue/vue3-jest@28.x
をインストールしています。
GitHub - vuejs/vue-jest: Jest Vue transformer
$ npm install --save-dev @vue/vue3-jest@28
次に、テストに使用するテスト環境のためののライブラリです。
一度テストを実行したときのエラーにかかれてあったため、追加でインストールしました。
デフォルトではnode
の環境を使用するが、コンポーネントのテストをする場合はjsdom
が必要なようです。
npm install --save-dev jest-environment-jsdom
ここでVueコンポーネントのテストに必要なライブラリは揃いました。
以下は不要なのですが、JavaScriptコードをテストするための準備になります。
JavaScriptテスト
jestでES6(ES2015)で書かれたJavaScriptコードをテストするためには、BabelをCommonJSに変換が必要だそうです。この変換を行うためbabel-jest
をインストールします。@babel/core
はbabel-jestを使用するためにインストールしています。
$ npm install --save-dev babel-jest@28 $ npm install --save-dev @babel/core
babelのプラグインでコードの変換する際のターゲット環境を設定できます。この環境のプリセットを設定できるライブライをインストールします。
npm install --save-dev @babel/preset-env
Jestの設定
package.jsonにscriptsでテスト実行コマンドを追加します。 今回は/tests
ディレクトリ以下にテストコードを作成していくため、jest tests
としています。
{ "scripts": { "test:unit": "jest tests" } }
他の設定もpackage.jsonにまとめて追加します。jest.config.jsonという名前で別ファイルを準備してもいいみたいです。
- testEnvironment: テスト環境
- testEnvironmentOptions: テスト環境オプション
- moduleFileExtensions: モジュールが使用するファイル拡張子
- transform: トランスフォーマーが変換するファイルの指定
- moduleNameMapper: 正規表現でimportされたモジュール名にマッチするものを置き換え
import Component from ../../src/components/HelloWorld
と書いていたがsrc/
までのパスを@/
で書くことが出来るようになります。
{ "jest": { "testEnvironment": "jsdom", "testEnvironmentOptions": { "customExportConditions": [ "node", "node-addons" ] }, "moduleFileExtensions": [ "js", "json", "vue" ], "transform": { ".*\\.(js)$": "babel-jest", ".*\\.(vue)$": "@vue/vue3-jest" }, "moduleNameMapper": { "^@/(.*)$": "<rootDir>/src/$1" } } }
Babelの設定
babelの設定もpackage.jsonに書いていきます。babel.config.jsという名前で別ファイルを準備してもいいみたいです。
- presets: プリセットとして利用するプラグインのリスト
- node: current とすることで、現在のnode.jsに合わせたコードに変換をしてくれる
{ "babel": { "presets": [ [ "@babel/preset-env", { "targets": { "node": "current" } } ] ] } }
これでやっとテストの準備ができました。(実際は何度もテスト実行し、足りていないライブラリや、設定を追加していきました。)
テストコード作成
ここからはチュートリアルに従って、テストコードを書いていきます。Options API
のスタイルで書かれていたが、新しいComposition APIのスタイルに変更しています。
本来はテストコードを作成し、そのテストが通るように修正していくのですが、こちらは全ての修正が完了した状態になります。
テスト対象コンポーネント
/src/components/TodoApp.vue
<script setup> import { ref } from "vue" const todos = ref([ { id: 1, text: "Learn Vue.js 3", completed: false } ]); const newTodo = ref(""); const createTodo = () => { todos.value.push( { id: 2, text: newTodo, completed: false } ); }; </script> <template> <div> <div v-for="todo in todos" :key="todo.id" data-test="todo" :class="[todo.completed ? 'completed' : '']"> {{ todo.text }} <input type="checkbox" v-model="todo.completed" data-test="todo-checkbox" /> </div> <form data-test="form" @submit.prevent="createTodo"> <input data-test="new-todo" v-model="newTodo" /> <input type="submit"> </form> </div> </template>
テストコード
/tests/unit/TodoApp.spec.js
import { mount } from "@vue/test-utils" import TodoApp from "@/components/TodoApp" test("renders a todo", () => { const wrapper = mount(TodoApp); const todo = wrapper.get("[data-test='todo']"); expect(todo.text()).toBe("Learn Vue.js 3"); }) test("creates a todo", async () => { const wrapper = mount(TodoApp); await wrapper.get("[data-test='new-todo']").setValue("New todo"); await wrapper.get("[data-test='form']").trigger("submit"); expect(wrapper.findAll("[data-test='todo']")).toHaveLength(2); }) test("completes a todo", async () => { const wrapper = mount(TodoApp); await wrapper.get("[data-test='todo-checkbox']").setValue(true); expect(wrapper.get("[data-test='todo']").classes()).toContain("completed"); })
テスト実行
$ npm run test:unit > vue-test-project@0.0.0 test:unit > jest tests PASS tests/unit/TodoApp.spec.js ✓ renders a todo (21 ms) ✓ creates a todo (8 ms) ✓ completes a todo (5 ms) Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 0.887 s, estimated 1 s Ran all test suites matching /tests/i.
カバレッジ出力
簡単な見方としては
- Stmts: C0 実行された行の網羅率
- Branch: C1 ifなのど分岐された処理の網羅率
- Funcs: 各関数呼び出し網羅率
- Lines: ファイルの各実行可能行が実行されたかの網羅率。Stmts と同じ?
$ npm run test:unit -- --coverage > vue-test-project@0.0.0 test:unit > jest tests --coverage [vue-jest]: Not found tsconfig.json. PASS tests/unit/TodoApp.spec.js ✓ renders a todo (22 ms) ✓ creates a todo (8 ms) ✓ completes a todo (4 ms) -------------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s -------------|---------|----------|---------|---------|------------------- All files | 100 | 100 | 100 | 100 | TodoApp.vue | 100 | 100 | 100 | 100 | -------------|---------|----------|---------|---------|------------------- Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 0.903 s, estimated 1 s Ran all test suites matching /tests/i.
/coverage/lcov-report/index.html
にレポートも出力されるようです。便利。
ローカル環境 kubernetesでWebアプリ起動
kubernetesの基礎習得のため、Webアプリの起動にトライした記事になります。
goで作成したWebアプリをkubernetesクラスタで起動し、DBのデータの追加・参照できるようににしていきます。
環境
OS: Windows10 docker: 20.10.10 kind: 0.17.0
事前準備
kubernetesクラスタの準備とDBを起動します。お試しなので全てローカルで起動します。クラウドを使用した場合料金がかかるので、一旦試してみたい方はこの方法で試すと良いかなと思います。
クラスタ起動
kind を使用し、Dockerコンテナでマルチノードのクラスタを起動します。プライベートなコンテナレジストリからイメージを取得するため、認証情報(deploy token)をsecret.json経由で与えています。
kind-cluster.yaml
kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 name: webapp-cluster nodes: - role: control-plane extraMounts: - containerPath: /var/lib/kubelet/config.json hostPath: ./secret.json - role: worker extraMounts: - containerPath: /var/lib/kubelet/config.json hostPath: ./secret.json - role: worker extraMounts: - containerPath: /var/lib/kubelet/config.json hostPath: ./secret.json
secret.json
{ "auths": { "registry.gitlab.com": { "auth": "****" } } }
$ kind create cluster --config kind-cluster.yaml $ docker ps --format "{{.ID}}\t{{.Image}}\t{{.Names}}" 46911f618580 kindest/node:v1.25.3 webapp-cluster-control-plane 1bcddfaf552f kindest/node:v1.25.3 webapp-cluster-worker aac1c231d95d kindest/node:v1.25.3 webapp-cluster-worker2
クラスタを起動すると、1つのマスタ(control-plane)と2つのワーカ(woker)コンテナが起動していることが確認できます。
DB起動
kindで起動すると3つのコンテナが作成され、kind
というDockerネットワークが作成されます。このネットワーク内に、DBコンテナを起動していきます。DBはPostgres14を使用します。
$ docker network ls NETWORK ID NAME DRIVER SCOPE d67081ee3cc5 bridge bridge local d705790474c7 host host local 62f01985424e kind bridge local ⇐ これ 0d2e79587807 none null local # DBコンテナ起動 # --nat=kind ネットワーク指定 # -e POSTGRES_PASSWORD=password パスワードを環境変数で設定 $ docker run --name db -e POSTGRES_PASSWORD=password -d --net kind postgres:14 $ docker ps --format "{{.ID}}\t{{.Image}}\t{{.Names}}" 6aaa1e918b9f postgres:14 db 46911f618580 kindest/node:v1.25.3 webapp-cluster-control-plane 1bcddfaf552f kindest/node:v1.25.3 webapp-cluster-worker aac1c231d95d kindest/node:v1.25.3 webapp-cluster-worker2
DB、テーブル作成
コンテナへ接続し、psqlコマンドで直接作成していきます。
# コンテナ接続 $ docker exec -it <コンテナID> bash # Postgres DB接続 $ psql -U postgres # DB作成 create database sample; # DB切り替え \c sample # テーブル作成 create table users( id serial, name varchar );
Webアプリの作成
golang echoフレームワークでuserの作成、参照を行えるエンドポイントを定義します。
- GET: /users/:id パスで指定されたidのユーザを返す
- POST: /users ユーザを1件追加し、追加されたユーザを返す
src/server.go
package main import ( "net/http" "github.com/labstack/echo/v4" "gitlab.com/test/kubernetes-web-app/src/handler" ) func main() { handler.Init() e := echo.New() e.GET("/users/:id", handler.GetUser()) e.POST("/users", handler.AddUser()) e.Logger.Fatal(e.Start(":1323")) }
src/handler/user.go
package handler import ( "database/sql" "fmt" "net/http" "os" "github.com/labstack/echo/v4" _ "github.com/lib/pq" ) type User struct { Id int `json:"id"` Name string `json:"name"` } var content string var Db *sql.DB func Init() { var err error Db, err = sql.Open( "postgres", fmt.Sprintf( "host=%s port=%s user=%s password=%s dbname=sample sslmode=disable", os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), ), ) if err != nil { panic(err) } } func GetUser() echo.HandlerFunc { return func(c echo.Context) error { id := c.Param("id") user := User{} if err := Db.QueryRow("SELECT id, name FROM users where id = $1", id).Scan(&user.Id, &user.Name); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err) } return c.JSON(http.StatusOK, user) } } func AddUser() echo.HandlerFunc { return func(c echo.Context) error { name := c.FormValue("name") newId := 0 if err := Db.QueryRow("INSERT INTO users(name) VALUES($1) RETURNING id", name).Scan(&newId); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err) } user := User{} if err := Db.QueryRow("SELECT id, name FROM users where id = $1", newId).Scan(&user.Id, &user.Name); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err) } return c.JSON(http.StatusOK, user) } }
Dockerイメージの作成
作成したアプリをDockerイメージ化して、コンテナレジストリへ追加します。リポジトリへPushしたときに自動で実行されるようGitlab-runner設定と、.gitlab-ci.ymlファイルでそのコマンドを記載しています。ただこのファイルでは Dockerイメージにlatestタグしか付かず、上書きされる形になるので、ビルドイメージごとにタグを切り替えていけるよう修正が必要です。
Dockerfile
apiVersion: v1 kind: Service metadata: name: webapp-service spec: ports: - port: 1323 targetPort: 1323 protocol: TCP name: http selector: app: webapp type: LoadBalancer
.gitlab-ci.yml
stages: - build image-build: stage: build tags: - docker image: docker:latest services: - docker:dind script: - docker info - docker build -t ${CI_REGISTRY}/test/kubernetes-web-app . - docker tag ${CI_REGISTRY}/test/kubernetes-web-app ${CI_REGISTRY}/test4038/kubernetes-web-app:latest - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker image push --all-tags ${CI_REGISTRY}/test/kubernetes-web-app
kubernetes マニフェスト作成、起動
Webアプリをdeploymentで起動し、そのPodへのアクセスを分散するめLoadbalancerを起動します。DBの接続情報であるuser/paswordはコードに直接記載したくないので、Secretを作成しそこから取得できるようにしています。
deployment, service
apiVersion: apps/v1 kind: Deployment metadata: name: webapp-deployment labels: app: webapp spec: replicas: 3 selector: matchLabels: app: webapp template: metadata: labels: app: webapp spec: containers: - name: webapp image: registry.gitlab.com/test4038/kubernetes-web-app:latest ports: - containerPort: 1323 env: - name: DB_HOST value: db - name: DB_PORT value: "5432" - name: DB_USER valueFrom: secretKeyRef: name: webapp-secret key: user - name: DB_PASSWORD valueFrom: secretKeyRef: name: webapp-secret key: password --- apiVersion: v1 kind: Service metadata: name: webapp-service spec: ports: - port: 1323 targetPort: 1323 protocol: TCP name: http selector: app: webapp type: LoadBalancer
secret
.data
に key : value
の形で複数の値を設定できます。値はbase64でエンコードしてから設定する必要があります。
apiVersion: v1 kind: Secret metadata: name: webapp-secret type: Opaque data: user: cG9zdGdyZXMK # postgres password: cGFzc3dvcmQK # epassword
マニフェストが作成できたので、kubernetesクラスタでアプリを起動していきます。
# Secret作成 $ kubectl apply -f secret.yaml # マニフェストから起動 $ kubectl apply -f deployment.yaml $ kubectl apply -f service.yaml
動作確認
curlコマンドでHTTPリクエストし、userの追加、参照が出来ることを確認しています。
# ポートフォワーディング $ kubectl port-forward service/webapp-service 1323:1323 # アクセス確認 # POST user作成 $ curl -X POST localhost:1323/users -d 'name=hoge' {"id":1,"name":"hoge"} # GET user参照 $ curl localhost:1323/users/1 {"id":1,"name":"hoge"}
kubernetesでwebアプリケーションを起動
kubernetes の基礎的な勉強の一環で、Webアプリケーションの構築からアクセスまでを実践しています。
構成
クラスタの構築にはkind
を使用し、ローカル環境でDockerコンテナを使い3つのノードを起動します。(Dockerがインストールされていること)
アプリは Go の WebFramework Echo を使用して簡単なものを作成し、Deploymentで3つのPodを起動させます。
アクセスはLoadBalancerで各Podに分散するようになっています。
コードをGitLabで管理していたため、DockerイメージはGitLabのコンテナレジストリを使用しています。
Webアプリケーション作成
まずは直接ローカルでWebアプリケーションを起動して、動作確認してみます。
GETメソッドで2つエンドポイントを定義した簡単なものです。
- / : HelloWorld を返す
- /users : クエリパラメータで受け取った
name
,email
値を返す
main.go
package main import ( "net/http" "github.com/labstack/echo/v4" ) type User struct { Name string `json:"name" query:"name"` Email string `json:"email" query:"email"` } func main() { e := echo.New() e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello, World!") }) e.GET("/users", func(c echo.Context) error { u := new(User) if err := c.Bind(u); err != nil { return err } return c.JSON(http.StatusOK, u) }) e.Logger.Fatal(e.Start(":1323")) }
動作確認
2つのエンドポイントにアクセスし、レスポンスが返ってくることを確認
# アプリケーション起動 $ go run main.go # アクセス $ curl http://localhost:1323/ Hello, World! $ curl http://localhost:1323/users?name=hoge&email=hoge@example.com {"name":"hoge","email":"hoge@example.com"}
Dockerイメージ作成
kubernetesで起動させるためDockerイメージ化し、コンテナ上で起動できるか確認
Dockerfile
FROM golang:1.19.5-buster as build WORKDIR /app COPY go.mod ./ COPY go.sum ./ COPY server.go ./ RUN go mod download RUN go build -o /webapp FROM gcr.io/distroless/base-debian11 WORKDIR / COPY --from=build /webapp /webapp USER nonroot:nonroot ENTRYPOINT ["/webapp"]
コンテナで起動して動作確認
同じくコンテナでもアクセスを確認
# イメージビルド $ docker image build -t webapp . # コンテナ起動 $ docker run -p 1323:1323 webapp # アクセス $ curl http://localhost:1323/ Hello, World! $ curl http://localhost:1323/users?name=hoge&email=hoge@example.com {"name":"hoge","email":"hoge@example.com"}
コンテナレジストリへDockerイメージのpush
GitLab Runner を使用して、パイプライン実行時にイメージのbuild,コンテナレジストリへのpush を行います
Runner は自前で起動せずGitLabのSharedRunner
を有効化し、共有されているものを使用します
.gitlab-ci.yml
stages: - build image-build: stage: build tags: - docker image: docker:latest services: - docker:dind script: - docker info - docker build -t ${CI_REGISTRY}/kubernetes-web-app:${CI_COMMIT_SHA} . - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker image push ${CI_REGISTRY}/kubernetes-web-app:${CI_COMMIT_SHA}
GitLab DeployTokenの発行
コンテナレジストリからDockerイメージを取得するため、DeployTokenを発行します
「Setting」⇒「Repository」⇒「Depoy tokens」
Pull an Image from a Private Registry | Kubernetes
kuberntesクラスタの起動
クラスタの定義はkind-cluster.yaml
で定義し,Dockerコンテナで起動。GitLabのコンテナレジストリから、Dockerイメージを取得するためconfig.jsonをマウントしてクラスタに認証情報を与えます。
config.json
発行したDeployTokenを{username}:{password}
の形式でbase64エンコードしたものをauth
に使用します。
Scopeはread_registry
が必要です。
# DeployToken base64エンコード $ echo -n {username}:{password} | base64 Z2l*******enZf # config.jaon { "auths": { "registry.gitlab.com": { "auth": "Z2l*******enZf" } } }
kind-cluster.yaml
config.json を各ノードにマウントし、GitLabの認証情報を与えます
kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 name: webapp-cluster nodes: - role: control-plane extraMounts: - containerPath: /var/lib/kubelet/config.json hostPath: ./secret.json - role: worker extraMounts: - containerPath: /var/lib/kubelet/config.json hostPath: ./secret.json - role: worker extraMounts: - containerPath: /var/lib/kubelet/config.json hostPath: ./secret.json
ノードの定義ファイルが作成できたら、クラスタを起動していきます
$ kind create cluster --config kind-cluster.yaml
Pod,Serviceリソースの作成, アクセス確認
Deployment, Service にマニフェストを作成し、クラスタ上で起動します。kindで作成されるクラスタでは、LoadBalancerを作成してもコンテナ外からアクセス出来ないため、一時的にポートフォワーディングを行いアクセスする。
deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: webapp-deployment labels: app: webapp spec: replicas: 3 selector: matchLabels: app: webapp template: metadata: labels: app: webapp spec: containers: - name: webapp image: registry.gitlab.com/kubernetes-web-app:latest ports: - containerPort: 1323
service.yaml
apiVersion: v1 kind: Service metadata: name: webapp-service spec: ports: - port: 1323 targetPort: 1323 protocol: TCP name: http selector: app: webapp type: LoadBalancer
# リソース作成 $ kubectl apply -f deployment.yaml $ kubectl apply -f service.yaml # ポートフォワーディング $ kubectl port-forward service/webapp-service 1323:1323 # アクセス $ curl http://localhost:1323/ Hello, World! $ curl http://localhost:1323/users?name=hoge&email=hoge@example.com {"name":"hoge","email":"hoge@example.com"}
SQL 縦持ち、横持ち変換
縦持ち、横持ちテーブルの相互変換方法
こちらで簡単に、SQLの確認を行えます。(paiza.io MySQL Online)
縦 ⇒ 横
使用するテーブル
id | child_id | name |
---|---|---|
1 | 1 | "1-1" |
1 | 2 | "1-2" |
2 | 1 | "2-1" |
2 | 2 | "2-2" |
2 | 3 | "2-3" |
3 | 1 | "3-1" |
3 | 2 | "3-2" |
SQL MAX句は、GROUP BY でグループ化した場合は集約関数を使う必要があるため使用しているだけで、関数としての意味はないです。
select id, MAX(CASE WHEN child_id = 1 THEN name END) as child_id_1, MAX(CASE WHEN child_id = 2 THEN name END) as child_id_2, MAX(CASE WHEN child_id = 3 THEN name END) as child_id_3 FROM vertical GROUP BY id;
実行結果
id | child_id_1 | child_id_2 | child_id_3 |
---|---|---|---|
1 | "1-1" | "1-2" | NULL |
2 | "2-1" | "2-2" | "2-3" |
3 | "3-1" | NULL | NULL |
横 ⇒ 縦
使用するテーブル
id | child_id_1 | child_id_2 | child_id_3 |
---|---|---|---|
1 | 1-1 | 1-2 | |
2 | 2-1 | 2-2 | 2-3 |
3 | 3-1 |
SELECT * FROM ( SELECT id, 1 as child_id, child_id_1 as name FROM horizon UNION ALL SELECT id, 2 as child_id, child_id_2 as name le FROM horizon UNION ALL SELECT id, 3 as child_id, child_id_3 as name FROM horizon ) AS vertical WHERE name is NOT NULL;
実行結果
id | child_id | name |
---|---|---|
1 | 1 | 1-1 |
2 | 1 | 2-1 |
3 | 1 | 3-1 |
1 | 2 | 1-2 |
2 | 2 | 2-2 |
2 | 3 | 2-3 |
標準出力、標準エラー出力 をファイルに出力する
# 標準出力 $ echo hogehoge > out.txt # 標準エラー出力 $ echoo 2> out.txt # 標準出力 & 標準エラー出力 $ echo hogehoge > out.txt 2>&1 # ※追記したい場合 $ echo hogehoge >> out.txt 2>>&1 # ※出力を破棄する場合 $ echo hogehoge > /dev/nulll 2>&1