koukiblog

たぶんweb系の話題

2020年まとめ

仕事の区切りが悪く、年末年始感があまりなかった。今やってるプロジェクトが一区切りついたらまとめたい。(特に忙しいわけではない)

最近は仕事以外の趣味に使う時間を増やしていきたい気持ち。特に体動かす系は、今のうちにどのくらい技術習得できるかが今後の楽しみに影響しそう。 仕事、というかソフトウェア開発は体力関係なくずっと出来そうなので、気長にやっていく。

Nginx Ingress Controllerには2つの実装があるので注意

はじめに

Nginx Ingress Controllerには2つの実装があります。

Kubernetesチーム管理のkubernetes/ingress-nginx(以下ingress-nginx) github.com

Nginxチーム管理のnginx/kubernetes-ingress(以下nginx-ingress)

github.com

ingress-nginx, nginx-ingressと呼び分けているのは、両者のドキュメントで Helmを利用したインストール方法でnameとして採用されているからです。

2つの違い

Nginx側のレポジトリ にドキュメントがあります。

kubernetes-ingress/nginx-ingress-controllers.md at master · nginxinc/kubernetes-ingress · GitHub

基本的な設定は似ていますが、マニフェストの書き方が異なる箇所が多く互換性はありません。Nginxの商用製品を使う場合はnginx-ingressが必須です。

ingress-nginxに関するドキュメントは、 kubernetes.github.ioでホストされています。 kubernetes.github.io

nginx-ingressは、docs.nginx.com でホストされています。 docs.nginx.com

annotationなど設定値で検索すると両者がヒットするとこがあるので、間違えないように気を付ける必要があります。

stable/nginx-ingress について

古い記事では、下記のように stable/nginx-ingressを利用する記述がありますが、stable/nginx-ingress はDeprecatedになっており、新規で利用してはいけません

helm install stable/nginx-ingress

stable/nginx-ingressKubernetesコミュニティの Ingress Controller なので、移行先は ingress-nginx です。

https://github.com/helm/charts/tree/master/stable/nginx-ingress

BigQueryで日付の欠損を補完したレポートを作成する

BigQueryで日付の欠損を補完したレポートを作成するときの方法です。過去何度かやったのだけどその度に調べたり思い出して対応してたのでここに残しておきます。

たとえば

テーブル1

日付 属性1 金額A
2020-07-01 A 500
2020-07-03 B 500

テーブル2

日付 属性1 金額B
2020-07-01 B 100
2020-07-02 B 500

という2つのテーブルから下記のような結果を出したいとします。(例としてはめちゃくちゃなんですが、やりたいことは察してください)

日付 属性1 金額A 金額B
2020-07-01 A 500 100
2020-07-01 B
2020-07-02 A
2020-07-02 B 500
2020-07-03 A
2020-07-03 B 500

日付を作成する

GENERATE_DATE_ARRAY関数で日付の配列を作成することができます。UNNEST関数で展開することでテーブルとして利用することができます。

SELECT
base_date
FROM
UNNEST(GENERATE_DATE_ARRAY( DATE("2020-07-01"), DATE("2020-07-03"))) AS base_date

属性のパターンを作成し、クロスジョイン

属性の取り得るパターンを事前に作成し、日付とクロスジョインします。(属性のパターンは、SELECTで作成するのではなく、マスターテーブルから取得するのが正しいです)

WITH date_series AS (
SELECT
base_date
FROM
UNNEST(GENERATE_DATE_ARRAY( DATE("2020-07-01"), DATE("2020-07-03"))) AS base_date
), dimensions AS (
SELECT "A" as attr1
UNION ALL
SELECT "B"
),base AS (
SELECT
base_date,
attr1
FROM
date_series, dimensions
)

SELECT
*
FROM
base

このSQLで下記のようなテーブルを作成することができます

date attr1
2020-07-01 A
2020-07-01 B
2020-07-02 A
2020-07-02 B
2020-07-03 A
2020-07-03 B

データテーブルとjoinする

あとは、データが入っているテーブルとJOINすれば完了です。WITH句は先ほどと同じです。

WITH date_series AS (
SELECT
base_date
FROM
UNNEST(GENERATE_DATE_ARRAY( DATE("2020-07-01"), DATE("2020-07-03"))) AS base_date
), dimensions AS (
SELECT "A" as attr1
UNION ALL
SELECT "B"
),base AS (
SELECT
base_date,
attr1
FROM
date_series, dimensions
)

SELECT
base.base_date,
base.attr1,
テーブル1.金額A
テーブル2.金額B
FROM
base
LEFT OUTER JOIN 
テーブル1
ON
base.base_date = テーブル1.base_date
AND
base.attr1 = テーブル1.attr1
LEFT OUTER JOIN
テーブル2
ON
base.base_date = テーブル2.base_date
AND
base.attr1 = テーブル2.attr1

VALORANTをインストールするとキーボードが効かなくなる場合がある

はじめに

VALORANTをインストールすると、VALORANTと一緒にインストールされるアンチチートツールVanguardによってCtrl2Capがチートツール扱いされ、ドライバエラーになり、なんとキーボードが無効化されるのでその注意喚起です。 この事象はRedditでも報告されていました。

www.reddit.com

対応方法

キーボードが無効化されるのでソフトウェアキーボードでWindowsを操作する必要があります。 ログイン画面では右下からソフトウェアキーボードを表示することができ、ログイン後はタスクバー右クリックで「タッチキーボードボタンを表示」を有効化することでソフトウェアキーボードを有効化することができます。 タスクバーからVanguardを右クリックしてUninstallを選択してアンインストール、その後OS再起動でキーボードが有効になります。

VALORANTをPlayするには

おそらく、ctrl2capをアンインストールするしか方法はありません。僕はctrl2cap無しでWindowsは扱えないのでVALORANTはしばらく見送りました。

ちなみに

キーボード故障かと思って新しいキーボード買ってしまったのですが、結果的には買い替えるよい口実になりました。

追記

アンチチートツールについてはすでに話題になってました maruhoi.com

KubernetesでPodに指定したServiceAccountを削除したいときは"default"を指定する

KubernetesでPodに指定したServiceAccountを削除したいときにはまったのでメモ

たとえばこんな感じでPodにServiceAccountを付与したとき

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
    spec:
      serviceAccountName: "foo"
      containers:
        - name: test
          image: busybox
          command:
            - sleep
            - "600"

サービスアカウントを指定を削除するために"serviceAccountName"要素を削除したマニフェストを作って適用したとする

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
        - name: test
          image: busybox
          command:
            - sleep
            - "600"

この場合、 serviceAccountName: "foo" は残ってしまう。未指定の場合は過去の設定をそのまま引き継いでしまう。明示的にserviceAccountをリセットする必要がある。

このときに、 serviceAccountName: "" のように空白を指定すると結局無視されてしまうので注意が必要。serviceAccountのデフォルトは"default"というserviceAccountなので、 serviceAccountName: "default" を指定する必要がある。

これが正解。

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
    spec:
      serviceAccountName: "default"
      containers:
        - name: test
          image: busybox
          command:
            - sleep
            - "600"

ちなみに、ServiceAccountNameを指定すると、ServiceAccountNameだけではなく、ServiceAccountという要素にも値が設定される。これはServiceAccountの方は既にDeprectedになっている古いAPIで互換性のために値がコピーされているだけなので気にしなくて良い。PodのSpecを見ると確認することができる。

https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#pod-v1-core

GCPのPubSubをGoでsubscribeするときにgraceful shutdownのようなことをする

GCPのPubSubのTopicをGoでsubscribeするとき、ライブラリ( https://github.com/googleapis/google-cloud-go ) を使えば下記のように簡単に記述できます

client, err := pubsub.NewClient(ctx, "project-id")
if err != nil {
    log.Fatal(err)
}

sub := client.Subscription("subscription1")
err = sub.Receive(ctx, func(ctx context.Context, m *pubsub.Message) {
    fmt.Println(m.Data) // process message 
    m.Ack() // Acknowledge that we've consumed the message.
})
if err != nil {
    log.Println(err)
}

しかしこれではTopicのメッセージを処理中に、コンテナが停止するなど何らかの理由でサーバーが停止するときに処理は行なっているもののAckを返していない、という状態になってしまいます。

シグナルハンドリングを行い、停止までに一定の猶予を設けることで、webサーバでよくあるgraceful shutdownのような挙動にするには、こんな感じにします

cctx, cancel := context.WithCancel(ctx)
sub := client.Subscription("subscription1")
err := sub.Receive(cctx, func(ctx context.Context, msg *pubsub.Message) {
  fmt.Println(m.Data) // process message 
  m.Ack() // Acknowledge that we've consumed the message.
}

quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-quit
// Start shutdown process
cancel()
// Waiting pubsub receive shutdown 2 seconds..
time.Sleep(2000 * time.Millisecond)

Receive関数渡したcontextを、SIGINTをハンドリング待ってから、cancelし、その完了を一定期間待つことで実現できます。この例では2秒待っていますがどのぐらい待つのかは処理内容によって考える必要があります。 メッセージの処理に時間かかり、処理中に設定したタイムアウト期間を越えることが想定される場合は、Receive関数に渡す無名関数内で必要に応じてcontext.Doneを確認するとよいと思います(あまりないと思いますが)

2019年まとめ

2019年の個人的なまとめ。2019年時点の認識を、あとで自分が読み返すのは有用そうだなーと思ったので残していくことにしました。

仕事

GCP上にKubernetesを基盤にしてマイクロサービスアーキテクチャを利用したシステムを開発していて、それを無事リリースすることができました。初期のアーキテクチャ設計とインフラ設計・構築をメインで担当していました。 アプリケーションはバックエンドはGo, フロントエンド(BFF)はNode.js を採用し、KubernetesはGKEを利用しました。

こう書くと流行りのバズワードを片っ端から採用したっぽく見えてしまってあれなんですが、一応それぞれ理由があって採用してます。

理想的にはそのサービスが順調に成長し、サービスの成長による様々な問題にKubernetesで構築したインフラ、マイクロサービスアーキテクチャを採用したアプリケーション群がどう対応していけるのか観察したいなーと考えていたが、サービス自体はずっと低調で運用にあたって特に新たに得るものはなかったのがなかなか難しいところ。 GKEで本番運用するための基本的なところは抑えられたかなーとは思っているけど、まだ何か落とし穴ありそうで怖い。

weave/flux使ってCD構築したり

Weave Fluxを利用してGKE環境のCDを構築した - koukiblog

Node.jsのコンテナを良い感じにしたりしていました

KubernetesにNodeJSアプリをデプロイするときにやったこと - koukiblog

透過型のL7プロキシが欲しくてIstioを採用したのだけど、これはちょっと失敗でした。いま作り直すならIstioは外すかな・・

読書

今年一番読んでよかった本は「イノベーターズ」でした。コンピューターが存在せず人が計算していたところから、現代のインターネットにたどり着くまでの歴史が非常に面白く書かれています。

https://www.amazon.co.jp/dp/4062201771/ref=cm_sw_r_tw_dp_U_x_OpRcEbEWCS0

イノベーターズを読んだ結果、この本に続きがあるとしたらどうなるのかなーと考えるようになりました。やっぱAIとブロックチェーンなのかなーと思い、AIはもう今更感あるので、ブロックチェーンのキャッチアップ、勉強をちょっと始めました。

その他

Fortniteにはまり、ひたすらやってました。来年はもっとうまくなりたい。 グローバルだし、ソーシャルゲームのような柔軟な運営とアップデートだし、プレイヤー同士コミュニケーションもとれるし、AAAタイトルのようなクオリティだし、競技シーンも発達してるし、基本プレイ無料だし、もうこれでいいのでは?という感じです。

来年

Webサービス開発・運用は自分の得意分野として引き続きキャッチアップしつつ、ブロックチェーン注目しようかなと思っています。

調べてみると技術的にはインターネット周りの技術と地続きな感じで面白いし、今まで新しいもの好きのようなweb業界の人たちが否定的な一方、デー子で金融系システムの保守ずっとやってるような人は遠くない未来に確実に来るものとして捉えていたり非常に興味深いです。

Web業界の仕事は昔ながらのWeb + DBサーバな三層アーキテクチャのままのところが多そうだし、エンタープライズ系の案件のほうが技術的には先進的なものを扱えるようになっていくかもしれないなーと思ってます。