Blog.Valletta.io

日々やってきたことの備忘録的なやつ

gqlgenでsubscriptionを使う時websocketでハマった

端的にいうと下記のような感じでNewDefaultServerを使っていると

srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))

ここに書いてある通り下記のコードを足してもunable to upgrade *http.response to websocket websocket: 'Origin' header value not allowedとか言われてしまう。

srv.AddTransport(&transport.Websocket{
    Upgrader: websocket.Upgrader{
        CheckOrigin: func(r *http.Request) bool {
        // Check against your desired domains here
        return r.Host == "example.org"
    },
    ReadBufferSize: 1024,
    WriteBufferSize: 1024,
    },
})

理由はNewDefaultServer内で下記のようなコードが書かれているために

srv.AddTransport(transport.Websocket{
    KeepAlivePingInterval: 10 * time.Second,
})

gorilla/websocket内のこのへんでcheckSameOriginが入り落ちる。

checkOrigin := u.CheckOrigin
if checkOrigin == nil {
    checkOrigin = checkSameOrigin
}
if !checkOrigin(r) {
    return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed")
}

https://github.com/gorilla/websocket/blob/b65e62901fc1c0d968042419e74789f6af455eb9/server.go#L146

自分でNewして必要なものだけAddTransportしよう。

srv := handler.New(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))

同じようなハマり方してる人がいるかもしれないので残しておく。

Github Actionsを使ってPull Request毎にPreview環境を作った時のメモ

GitOps的なPreview環境が欲しいよねという話になり、k8s(EKS), Skaffold1.2.0, Helm3 を使ってチームで作った時の備忘録的なやつです。ついでにローカル開発に使える環境も作りました。

はじめに

スプリントレビューやデザインレビュー用にPull RequestごとのPreview環境あったら良いよねっていう話になって作った時の備忘録的なやつです

使ったプラットフォームや技術スタックやツールなど

  • AWS
  • EKS
  • terraform
  • Skaffold 1.2.0
  • Helm 3.0.0
  • jx (jx contextが便利だから使っていただけ、別いにいらないです。Docker for Mac使っているならマウスでポチポチすればコンテキスト切り替えれます)
  • Octant(EKSを使っていたためGKEのようにダッシュボードがいい感じじゃないので確認用に採用しました。とても使いやすい。)

    EKSかGKEか

    他のAWSサービスとの連携がしたいとか政治的な都合とかがなければGKEの方が絶対楽だと思います。 EKS自体のManagement feeが取られるのも微妙だなーと思っていましたが、今年6月からGKEも取るようなのでそこはまぁいいかという感じです。 一番きついのはPODに割当可能なIPアドレスの制限です。せめてt3.largeぐらいのインスタンスをNode Groupに設定しないと全然Preview環境が生やせません。 https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-eni.html

    まずはAWS上に環境構築

    terraformでやるのが良いかと思います。 ECR,IAM,各種ネットワーク、DBを共用するならRDS、あとはEKSクラスタなどをここで作ってしまいます。 今回はノードにt3.largeインスタンスを利用しました。 リソース的にはもっと小さいインスタンスで良かったんですが、使えるIPの個数に制限があるため大きめのものにしました。 t3.largeだと36個のIPが利用できるようです。 Fargate for EKSは今回ALBしか使えない関係で使いませんでした。 https://dev.classmethod.jp/cloud/aws/outline-about-fargate-for-eks/

    最低限必要なものや全体で使いそうなものをk8sクラスタに入れていく

  • grafana
  • mailhog
  • nginx-ingress
  • prometheus
  • sealed-secret(パスワードなどの管理用) あたりは /charts みたいなディレクトリを切ってパッケージごとにvalues.yamlを置いていってMakefileに下記のような感じで書いてインストールしていきました。
helm upgrade --install nginx-ingress stable/nginx-ingress -f charts/nginx-ingress/values.yaml --namespace kube-system --wait
helm upgrade --install sealed-secrets stable/sealed-secrets -f charts/sealed-secrets/values.yaml --namespace kube-system --wait

ローカルでまず試す時、docker for macKubernetesReset Kubernetes Cluseterボタンをポチッと押すだけで環境が初期化され、何度も1から入れ直して動作を確認するのにとても便利でした。 Makefileをちゃんと書いてくと再インストールも楽々です。 秘密情報の格納方法としてはsealed-secretが便利そうだったので使うことにしました。

Preview環境を構築する

Helm か Kustomizeか

Preview環境も生やしたいけど、Local開発にも流用したい。 そのうちStaging環境にも流用したいという感じだったので、そういうことが実現できるツールを探しました。 Kustomizeが王道っぽくシンプルだったのでKustomizeで始めたのですが、なんか痒いところに手が届かず無理やりsedとか使う感じになってたのでHelmに切り替えました。

local環境とpreviewの差異をどう扱うか

charts/a-base
charts/a-local
charts/a-preview

みたいに構成にしてbaseのtemplates以下に共通部分を書き、localやpreview側では Chart.yamlにdependenciesを定義しました。

dependencies:
- name: a-base
version: 0.1.0
repository: file://../a-base
alias: base

この状態で例えばpreviewのvalues.yamlに下記のような感じで書けば依存先のvalues.yamlを上書きできます。

base:
variantName: preview
api:
terminationGracePeriodSeconds: 1
imageName: "xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/a/api"
env:
APP_NAME: "preview-A_API"
DB_HOST: "your-db-host"

別途追加が必要な部分はtemplatesに追加すればいいです。

skaffold.yamlを書く

skaffoldを使って動かすのでskaffold.yamlを定義します。 デフォルトでskaffold devした時はローカルで動かしたいのでノリとしては下記yamlのような感じです。 preview用のものはprofilesで分けていて、profileを指定せずにskaffold dev -n a -v info --cleanup=false; helm delete a-local -n aした時は一番上に定義したもの(local用)が動くようにしてます。 --cleanup=false; helm delete a-local -n a とかしてるのはskaffold1.2がhelm3に対応していなかったためです。

... 省略 ....
apiVersion: skaffold/v2alpha2
kind: Config
build:
tagPolicy:
gitCommit: {}
artifacts:
- image: api
context: .
docker:
dockerfile: docker/api/Dockerfile
sync:
<<: *apiSync
local:
push: false
useBuildkit: true
concurrency: 0
deploy:
helm:
flags:
upgrade:
- --install
releases:
- name: a-local
chartPath: k8s/charts/a-local
wait: true
values:
base.api.imageName: api
base.worker.imageName: api
setValueTemplates:
base.nameOverride: a-local
base.fullnameOverrid: a-local
base.variantName: local
profiles:
- name: preview
build:
tagPolicy:
gitCommit: {}
artifacts:
- image: xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/a/api
context: .
docker:
dockerfile: docker/api/Dockerfile
- image: xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/a/front
context: .
docker:
dockerfile: docker/front/Dockerfile
buildArgs:
ENVIRONMENT: "preview"
API_ROOT: "https://pr{{.PR_NUMBER}}.api.dev-a.com/api"
local:
push: true
useBuildkit: true
concurrency: 0
deploy:
helm:
flags:
upgrade:
- --install
releases:
- name: a-preview-pr{{.PR_NUMBER}}
chartPath: k8s/charts/a-preview
wait: true
values:
base.api.imageName: xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/a/api
base.worker.imageName: xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/a/api
base.front.imageName: xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/a/front
setValueTemplates:
base.nameOverride: a-preview-pr{{.PR_NUMBER}}
base.fullnameOverride: a-preview-pr{{.PR_NUMBER}}
base.variantName: preview
base.api.env.APP_NAME: "pr{{.PR_NUMBER}}-A_API"
base.api.env.APP_URL: "https://pr{{.PR_NUMBER}}.api.dev-a.com"
base.ingress.api.host: "pr{{.PR_NUMBER}}.api.dev-a.com"
... 省略 ...

skaffold.yamlからPull Requestの番号をhelmに渡しています。これは後述するGithub Actionsから渡されている値です。

Github Actionsで動かす

Pull Requestからpreview環境を生やしたいわけなので、Github Actionsの設定を行います。 .github/workflows/create_preview.ymlとか適当に置いてそこに設定してみましょう。 本当はPushされたらすぐPreview環境が用意されると良いと思うのですが、今回はIPアドレスの制限もあるので欲しい時だけ作るようにしました。 /preview とコメントした時だけpreview環境を作成します。 こんな感じで判定できます。

if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/preview')

あとは環境変数にPull Requestの番号を格納し、諸々必要なツールをインストールしたらこんな感じでネームスペースを作ってあげて

- name: Create Namespace
run: |-
if [[ -z $(kubectl get ns | grep ^pr$PR_NUMBER) ]]; then
kubectl create ns pr$PR_NUMBER
fi

skaffoldでpreview環境を構築します。 構築が終わったらコメントにURLを出してあげると親切だと思います。

helm dependency update --skip-refresh k8s/charts/a-preview
skaffold run -v info -p preview -n pr$(PR_NUMBER)

Ingressを下記のような感じで定義していると、prXXX.dev-a.comみたいなノリでアクセスできるようになっていると思います。

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
... 省略 ...
spec:
rules:
- host: {{ .Values.ingress.api.host }}
http:
paths:
- backend:
serviceName: {{ template "a-base.api.fullname" . }}
servicePort: 80
path: /
... 省略 ...
{{- end }}

権限っぽいエラーが出た時

いざAWS上で動かそうとすると下記ようなエラーが出る場合、ClusterRole,ClusterRoleBinding,およびConfigMapの適切な設定が必要です。

...略...
could not get information about the resource: sealedsecrets.bitnami.com "a-preview-secret" is forbidden: User "ci" cannot get resource "sealedsecrets" in API group "bitnami.com" in the namespace
...略...

上記のような権限エラーが出たら、AWS側で定義してあるIAMユーザを元に適切設定を行ってください。

apiVersion: v1
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
data:
... 略 ...
mapUsers: |
- userarn: arn:aws:iam::xxxx:user/ci
username: ci
groups:
- ci
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ci-sealedsecrets-admin
rules:
- apiGroups: ["bitnami.com"]
resources: ["sealedsecrets"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ci-sealedsecrets-admin
subjects:
- kind: Group
name: ci
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: ci-sealedsecrets-admin
apiGroup: rbac.authorization.k8s.io

apiGroupsとかresourcesに設定する名前は kubectl api-resources で確認できます。 IAMユーザとk8s上のユーザのマッピングはこのあたりを参照してみてください クラスターのユーザーまたは IAM ロールの管理 EKSでの認証認可 〜aws-iam-authenticatorとIRSAのしくみ〜

Pull RequestがマージされたりCloseされたらちゃんと消す

消さないとリソースを食いつぶします。 これもGithub Actionsでやってしまいましょう。 namespaceを消せば終わりです。 例えばこんな感じです。

name: Close Preview
on:
pull_request:
types: [closed]
jobs:
close-preview:
name: Close Preview Environment
runs-on: ubuntu-18.04
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
steps:
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: Install kubectl
run: |-
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
chmod +x kubectl
sudo mv kubectl /usr/local/kubectl
- name: Set k8s context
run: |-
aws eks --region ap-northeast-1 update-kubeconfig --name a-dev
kubectl config get-contexts
- name: delete namespace
run: |-
echo $PR_NUMBER
if [[ -n $(kubectl get ns | grep ^pr$PR_NUMBER) ]]; then
kubectl delete ns pr$PR_NUMBER
fi

Octantで正常に動いているか確認してみる

別にkubectl使っても良いですが、Octantを利用するとブラウザで色々と楽に確認できるのでおすすめです。 インストールしてからoctantと実行するだけです。表示される情報は現在有効なKubernetesのコンテキストについてなので、「あれ?なんかおかしいな」と思ったらjx contextなどで今のコンテキストを確認しましょう。

octant
Octant

やってみた感想

  • EKSはGKEと比べてめんどくさい
  • IPアドレスの数の制限や、GKEだとある機能がなかったりなど面倒でした
  • Helmのテンプレート書きにくい
  • HelmというかGoのTemplateなんですが、 indent 4 みたいな書き方どうも苦手です
  • 使い勝手はとても良くてチームのリソース使ってやる価値はありました
  • これで新規メンバーが参画した時も「おま環」問題が発生しにくくなるだろうと思っています
  • 何より色々知見が貯まってよかったです

リモートワークするにあたって便利だったサービスやガジェットについて

はじめに

リモートワークを始めてからもう何年か経った。

何年かはエンジニアが1人か2人でBizが1人という環境だったのでウェブサービスやガジェットに拘らなくてもなんとかなっていた。

しかしここ1年1チーム7名ほど(スクラムマスター1人、エンジニア5人、デザイナ1人)のスクラムをちゃんと回すチームで働き始めて、今まで通りにやっていたら色々困ったのでどう解決してきたかを振り返っていく。

普段どんな感じで働いているか

基本的に月〜金1日あたり8時間というごく普通働き方をしている。(最初のうちは5時間程度だったけど)

月曜日〜木曜日はリモートで働いていて、金曜日だけはレトロスペクティブ(振り返り)と次週のスプリントのプランニングのために出社している。

オンラインでのコミュニケーションは基本的にSlackで、スプリントの各種セレモニーやミーティングの際はビデオチャットを繋ぐ感じ。

どんなことで困っていたか

色んな問題が発生して都度チームの協力のもと解決していったが特に大きかったものを2つほど挙げる。

マイクの音質

まずマイクの音質で困った。

こちらは自宅から一人で繋いでいるのに対して会社側は複数人いる。

MacBook Proの付属マイクや中途半端な品質のダイナミックマイクだと正直きつい。

ホワイトボード見えない問題

あと、ホワイトボードで何か始められるとカメラの品質や光の加減、ビデオチャット用のソフトウェアの問題でマジで何も見えない。

カメラを動かしてもらったり色々やってもらったりはしたがホワイトボードを使ってリモートで会議を行うのは無理ゲーだと思ったほうが良い。

どう解決していったか

レトロスペクティブにタイムラインというものを導入していたのでここに積極的に問題点を上げたり、Slackの分報に書いたりしていた。

タイムラインで1週間を振り返った後にKePTA(Keep, Problem, Try, Action)というものを話し合いながら出すのだけれど、Problem(チームの課題)として積極的に発生した問題について挙げていた。

KeepとProblemに挙げたものについてメンバーで投票し、得票数の多かったもの2つを掘り下げてTryとActionを考えるのだが、マイクもホワイトボードの問題もチームが課題として認識してくれていて、これで解決へ向かったと思っている。

マイクの音質問題の解決

これはちゃんとしたマイクを買うことで解決した。

Yeti Proというマイクがある。

これはUSBとXLR接続が可能で、指向性・無指向性が切り替え可能、しかもコンデンサマイクという理由で導入してもらった。

ポッドキャストとか通話にダイナミックマイクが良いというのもよく見かけたりするけど、1チームに7人ほどいて、それが2チームいる場合の全体の打ち合わせとかだと多分コンデンサマイクじゃないとゲインが確保できないんじゃないかと思う。

USB接続(誰でも接続できる)という点、集音性が高く無指向性にできる点がとても役立っていて、何言ってるか全然聞こえないみたいな問題はほぼなくなった。

ただ、マイクを固定しておかないとセットアップに手間がかかったりしてミーティングの開始が数分遅れたり、他チームに貸して移動することでケーブルの接触が悪くなったりしたので1チーム1つ買ったほうがいい。

ちなみにビデオチャットのツールとしてはGoogle Hangout Meetを使っている。

色々他のサービスも試したけどこれが音質や使い勝手など一番良かった。

特に画面の共有が便利で、全員オフィスにいる時でも画面共有目的で使ったりする。

ホワイトボード見えない問題の解決

レトロスペクティブの時は全員オフィスにいるので相変わらずホワイトボード+ポストイットを利用しているが、オンラインでミーティングを行う場合はほぼ物理ホワイトボードを使わなくなった。

Miroというオンラインホワイトボードを使うことで全てが解決した。

これはめちゃくちゃよくできてて、リアルタイムに共同編集できるし、接続中のメンバーのカーソルの位置も分かるし、付箋を貼り付けることもできる、スタンプなんかもつけることができる。

テンプレートもいっぱいあるしもう完璧に近い。

値段は一人あたり10$ぐらいするけどそれに見合う価値はある。

他に便利だったもの

Mac mini 2018。

メモリ換装できて持ち運び可能(MacBook Proと同じぐらい軽い)なのでディスプレイがあるならとても快適に作業できる。

毎週金曜日はMac miniHHKB HYBRID Type-sとmagic trackpad 2を持って出社してる。

まとめ

リモートワークは出社しなくていい代わりに、リモートであるが故のストレスが溜まることも多かったりするのでチームの協力を得ながら環境を良くしていくのも大事。

今回のチームではスクラムマスターがとてもしっかりしているのでレトロスペクティブをきっちりやる習慣があったけど、もしこれがなんちゃってスクラムだったら改善案を吸い上げる場さえなくてストレス溜まって辞めちゃってただろうなとか思った。

たぶんチーム開発+リモートワークを取り入れてるところで同じような問題が起こっていると思うので忘れないうちに書いておいた。

なんか思い出したらまた追記していくかも。