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 macのKubernetesはReset 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
などで今のコンテキストを確認しましょう。
やってみた感想
- 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 miniとHHKB HYBRID Type-sとmagic trackpad 2を持って出社してる。
まとめ
リモートワークは出社しなくていい代わりに、リモートであるが故のストレスが溜まることも多かったりするのでチームの協力を得ながら環境を良くしていくのも大事。
今回のチームではスクラムマスターがとてもしっかりしているのでレトロスペクティブをきっちりやる習慣があったけど、もしこれがなんちゃってスクラムだったら改善案を吸い上げる場さえなくてストレス溜まって辞めちゃってただろうなとか思った。
たぶんチーム開発+リモートワークを取り入れてるところで同じような問題が起こっていると思うので忘れないうちに書いておいた。
なんか思い出したらまた追記していくかも。