yikegaya’s blog

仕事関連(Webエンジニア)と資産運用について書いてます

GoとGraphQLでカレンダーアプリのサーバサイドを実装した

Vue.jsで作ったカレンダーアプリのバックエンドをGoとGraphQLで実装してみた。

github.com

以前に個人開発でGo、gorm、ginを使ってRESTのAPIサーバを作ったことがありkonnkaimoORマッパーにはgormを使ったので前回との差分はGraphQLくらい。

github.com

GoでのGraphQLのセットアップ

gqlgenと言うライブラリを使って実装を進めた。

github.com

概ねREADMEに書いてある通りにコマンドを叩いてセットアップ

go get github.com/99designs/gqlgen

go run github.com/99designs/gqlgen init

initコマンドでrails newとかnuxt create appみたいな感じで必要なフォルダが自動生成される。

後は

schema.graphqlsにクエリで使うスキーマを定義 →gqlgen generate →schema.resolvers.goにクエリの中で実行する処理を実装

と言う流れで開発していく。

予定登録での実装例

1 schema.graphqlに以下定義

type Schedule {
  id: Int
  userId: Int
  title: String
  content: String
  memo: String
    start_at: String
    end_at: String
}

input NewSchedule {
  userId: Int
  title: String
  content: String
  memo: String
    start_at: String
    end_at: String
}

type Query {
  schedules(userId: Int): [Schedule]
}

type Mutation {
  createSchedule(input: NewSchedule!): Schedule!
}

2 gqlgen generate実行

graph以下のファイルがschema定義に沿って自動生成/更新される

3 graph/schema.resolvers.goの編集

func (r *queryResolver) Schedules(ctx context.Context, userID *int) ([]*model.Schedule, error) {
    var schedules []*model.Schedule
    db, err = infrastructure.GetDB()
    if err = db.Where("user_id = ?", userID).Find(&schedules).Error; err != nil {
        return nil, err
    }
    return schedules, nil
}

func (r *mutationResolver) CreateSchedule(ctx context.Context, input model.NewSchedule) (*model.Schedule, error) {
    schedule := &model.Schedule{
        Title:   input.Title,
        Content: input.Content,
        Memo:    input.Memo,
        StartAt: input.StartAt,
        EndAt:   input.EndAt,
        UserID:  input.UserID,
    }
    db, err = infrastructure.GetDB()

    if err = db.Create(&schedule).Error; err != nil {
        return nil, err
    }

    return schedule, nil
}

トップ画面から確認

ブラウザでアクセスするとエディタでクエリを書いて実行確認できる。これはcurlやフロントエンドのコードいちいち書いてデバッグするより全然快適

感想

とりあえず簡単なqueryとmutationを実行するところまではできたけど複雑な用件出てきたときにGraphQLで対応できるかはまだ微妙。ただ学習コストそこまで高くはなさそうで軽い気持ちでRESTの代わりに採用しても割となんとか使えんじゃないかな。

セットアップはschema定義→コマンド叩く流れでサクサク作れていい感じだったけどこうやってschema定義→gqlgenでコード自動生成って流れはこのライブラリ固有の独特な流れなのかな?他の環境でもGraphQLサーバ作ってみないといまいちそこはわからん。。

後普段スクリプトばっか書いてるからまだGoがサクサク使えない感じある。Goもう少し書きたい。

Vue.jsでカレンダー作ってみた

Vue.jsでカレンダーライブラリを使わず自作のカレンダーを作り始めてみた。create-nuxt-appでプロジェクト作って、とりあえず

  • 月単位で日付表示
  • 月選択

まで実装できたので実装内容書いてみる。

見た目

ソースコード全体

GitHub - ikeyu0806/vue-calendar: Vue.js/GraphQLで開発した自作カレンダー

ざっくりと流れは

jsのDateオブジェクトで表示対象の月の日付情報を取得 →そこを起点にfor文で日付を加算していき月末までの日付を保持する配列を作る(カレンダーの行番号をkey、日付をvalueとした連想配列) →v-forに渡して描画

その他細かいところの実装でいうと

  • 現在の日付、曜日、月、年と先月末の日付をvueのdataオプションに持たせて「前の月」、「次の月」ボタンを押すと表示する月を切り替えられるようにした。
  • v-forのkeyを使って判定して日曜日の列のみ赤字で表示させた。こんな感じ

<th v-for="week in weeks" :key="week.id" :class="{sunday: week.id === 0}">{{ week.value }}</th>

<template>
  <div>
    <v-row justify="center" align="center">
      <v-col><h3 id="date-title">{{ dateTitle }}</h3></v-col>
      <v-col></v-col>
      <v-col></v-col>
      <v-col></v-col>
      <v-col>
        <div class="month-select">
          <span @click="prevMonth"><v-btn outlined>前の月</v-btn></span>
          <span @click="nextMonth"><v-btn outlined>次の月</v-btn></span>
        </div>
      </v-col>
    </v-row>
    <br />
    <v-row justify="center" align="center">
      <table>
        <thead>
          <tr>
            <th v-for="week in weeks" :key="week.id" :class="{sunday: week.id === 0}">{{ week.value }}</th>
          </tr>
          <tr v-for="(week, index) in calendars" :key="index">
            <td v-for="(day, index) in week" :key="index" :class="{sunday: index === 0}">
              {{ day.date }}
            </td>
          </tr>
        </thead>
      </table>
    </v-row>
  </div>
</template>

<style>
/* eslint-disable */
#date-title {
  margin-left: 60px;
}
.month-select {
  margin-right: 10px;
}
.sunday {
  color: red;
}
th {
    border: 1px solid #ddd;
    padding: 30px;
    text-align: center;
}
td {
    border: 1px solid #ddd;
    padding: 30px;
    text-align: center;
}
</style>

<script>
export default {
  data () {
    return {
      weeks: [
        { id: 0, value: 'SUNDAY' },
        { id: 1, value: 'MONDAY' },
        { id: 2, value: 'TUESDAY' },
        { id: 3, value: 'WEDNESDAY' },
        { id: 4, value: 'THURSDAY' },
        { id: 5, value: 'FRIDAY' },
        { id: 6, value: 'SATURDAY' }
      ],
      startDay: new Date(this.date().getFullYear(), this.date().getMonth(), 1).getDay(),
      startDate: new Date(this.date().getFullYear(), this.date().getMonth(), 1),
      currentMonth: this.date().getMonth(),
      currentYear: this.date().getFullYear(),
      lastMonthEndDate: new Date(this.date().getFullYear(), this.date().getMonth(), 0).getDate()
    }
  },
  computed: {
    dateTitle () {
      // jsのgetMonthが0-11なので1加算
      const month = this.currentMonth + 1
      const result = this.currentYear + '年' + month + '月'
      return result
    },
    calendars () {
      return this.renderCalendar()
    }
  },
  methods: {
    prevMonth () {
      this.currentMonth === 0 ? this.currentMonth = 11 : this.currentMonth--
      this.currentMonth === 11 && this.currentYear--
      this.lastMonthEndDate = new Date(this.currentYear, this.currentMonth, 0).getDate()
      const dt = new Date(this.currentYear, this.currentMonth, 1)
      this.startDate = dt
      this.startDay = dt.getDay()
      this.lastMonthEndDate = new Date(dt.getFullYear(), dt.getMonth(), 0).getDate()
    },
    nextMonth () {
      this.currentMonth === 11 ? this.currentMonth = 0 : this.currentMonth++
      this.currentMonth === 0 && this.currentYear++
      this.lastMonthEndDate = new Date(this.currentYear, this.currentMonth, 0).getDate()
      const dt = new Date(this.currentYear, this.currentMonth, 1)
      this.startDate = dt
      this.startDay = dt.getDay()
      this.lastMonthEndDate = new Date(dt.getFullYear(), dt.getMonth(), 0).getDate()
    },
    date () {
      return new Date()
    },
    year () {
      return this.date().getFullYear()
    },
    month () {
      return this.date().getMonth()
    },
    endDate () {
      return new Date(this.year(), this.month(), 0)
    },
    endDayCount () {
      return this.endDate().getDay()
    },
    renderCalendar () {
      const startDay = this.startDay
      const currentDate = this.startDate
      const lastMonthEndDate = this.lastMonthEndDate
      const calendars = []
      let lastDateCount = startDay - 1
      for (let i = 0; i < 5; i++) {
        const weekRow = []
        for (let day = 0; day < 7; day++) {
          if (i > 0 || (i === 0 && day >= startDay)) {
            weekRow.push({
              date: currentDate.getDate()
            })
            currentDate.setDate(currentDate.getDate() + 1)
          } else {
            weekRow.push({
              // 曜日を使ってつじつま合わせ
              date: lastMonthEndDate - lastDateCount
            })
            lastDateCount--
          }
        }
        calendars.push(weekRow)
      }
      return calendars
    }
  },
  components: {
  }
}
</script>

Vueインスタンス部分

Vue経験が浅いのでcomputed、data、methodをそれぞれどう振り分けるかパッとわからなかったけど

  • 「前の月」、「次の月」ボタンを押した時にデータバインディングしたいところ→data
  • dataオプションと同じようにデータの変更に反映するけどdataよりロジックが介入してくるところ→computed
  • computedから呼び出したいjsメソッド→methods

という風に振り分けた。この辺はざっくり組んだので後々リファクタリングするかも

style

create-nuxt-appする時にデザインテンプレートを選択できるので使ったことがないvuetifyを選択してみた。 この時点ではv-rowとv-colを使ったグリッドシステムのみ使用。カレンダーの見た目はCSS書いて作っている。

今後やりたいこと

  • ユーザログイン機能を作りたい
  • モーダルを表示させてそこから予定を登録したい
  • 登録された予定を表示
  • バックエンドを作ってデータを永続化したい
  • component分割してみたい

あたりかな。。他にもやろうと思えばいくらでも欲しい機能はあるけどボチボチ追加していく予定。

LPICレベル2に合格した

9月の末にLPICの201試験受けたけど続けて202試験も受けてきた。で、合格できたのでこれでレベル2に認定された。

201試験受けた時の感想

https://ikeyu0806.hatenablog.com/entry/2020/09/27/105729

202試験の内容

  • DNS
    • DNSの基本
    • BINDの基本設定
    • ゾーンファイルの管理
    • DNSサーバのセキュリティ
  • WEBサーバとプロキシサーバ
    • WEBサーバの設定
    • WEBサーバの監視とメンテナンス
    • プロキシサーバの設定
    • Nginxとリバースプロキシ
  • ファイル共有
    • Microsiftネットワーク
    • Sambaサーバの構築
    • NFSサーバの構築
  • ネットワーククライアントの管理
  • メールサービス
    • SMTPサーバの構築
    • メールのフィルタリング
    • POPとIMAP
  • システムセキュリティ
    • パケットフィルタリング
    • ルータの構成
    • FTPサーバのセキュリティ
    • OpenSSH
    • セキュリティ業務
    • OpenVPN

101-201に比べて割と普段業務でやってるようなWebの開発に絡んでくるところが多かった印象。

勉強方法

ググって202試験について調べると、なぜかレベル3より難しくてLPIC最難関とあちこちに書いてあったので

  • ping-tを模擬試験毎回90%取れるまで解く
  • コマ問も5周くらい回す
  • 白本の問題も全問解く
  • あずき本3周くらい読む

と、201以前に比べて結構念入りに勉強しておいた。101-201はping-tの選択問題8割くらい解ければ受かる感じだったけど実際202はこれでもそんなに余裕はない合格点だった。

感想

レベル1は勉強してて結構楽しい感じもあったんだけどレベル2はひたすら細かい設定ファイルの書き方暗記したりと基本的にはずっと苦行だったな。。

この試験受けてよかったかどうかでいうとLPICレベル1やAWS試験ほど直接業務で役に立ってる感じはないけど、最近はクラウドサービスでブラックボックス化されているようなレイヤーの知識も得られて自信はついた気はする。

「改訂2版 みんなのGo言語」読んだ

「改訂2版 みんなのGo言語」をざっと読んでみた。

初版が2019年8月発売でその時点での最新のGo言語事情がまとまった本。今2020年の12月なんで1年経ってるけどまあそこまで今の状況と解離してないはず。

この間GoでAPIサーバ作ったんだけどいまいち「Go言語よい!」みたいにならなかった。

GitHub - ikeyu0806/movie-info-backend: 映画情報、レビューサービス(バックエンド)

Go言語、速いし型があるしdockerとクラウドを使った今風のマイクロサービスと相性がいいのはわかるんだけど、Rubyで書いた時に比べて何倍も時間かかるしまだなんかいまいち良さわかってないかな。。という感じで終わったんだけどやっぱり評判いいしもう少し触ってみようかなと思ったのが読んだ動機。

感想

CLIツール

どのプラットフォームでも動くようにコンパイルできるのでCLIツール作るのに向いているらしい。RubyJavaみたいに言語を動かすためのランタイムの用意も不要だしこれは良さそうかな。。何か作ってみたい。

テスト

前回APIサーバ作った時はあまりいい感じにテストできなかったので色々と思うところはあった。

他の言語みたいにテストフレームワークとマッチャを使ってテストをする感じじゃなさそうだな。。というのは感じていたけどやっぱりメソッドは基本失敗したらエラーを返すように作ってテストコードではそのエラーを拾えるように設計していくのが良さそう。ここはまだ今後変わっていく可能性もあるかもしれないけど。

並行処理

並行処理が書きやすいのが人気の理由であるのでこれはちゃんと使ってみたい(使えてない) 最初はとりあえず片っ端から頭に「go」って付けちゃいそうだけど。

WEB開発

前回適当にginとgormで作ってそれも評判いいし、いいツールではあるはずだけど他にも良さそうなWEBフレームワークやORマッパー色々あるみたい。今度は別の触ってみるか。

コードフォーマッタ

go-fmtやgo-importでコードフォーマットするのが常識らしい。rubyのrubocopとかjsのESLintみたいなもの。個人開発の時は使ってなかったけどこれも今度は試そう。

今後

この本読み返しつつまた別のフレームワーク、ライブラリ使ってAPIサーバ作るか、何かしらのCLIツール作ってみたいかな。

SQLのカウントで地味にハマったこと

SQLで論理削除されていない件数を一行で出そうとしたら軽くハマったのでメモ。DBはpostgresql

論理削除フラグはdelfというカラムで0なら削除されていない。1なら削除済みと判別している。

別にgroup byかwhereでカラム指定すればいいんじゃと思うけどgroup byだとレコード分かれちゃうからダメで、3テーブルjoinさせているSQLで対象のテーブルの全レコード論理削除されてる場合も結果に含めたかったのでwhere delf = 0+LEFT JOINでもうまくいかなかった。

テーブル3のレコードが全て削除されている場合は0で出したかったけどこれだと結果から除外されてしまう↓

SELECT テーブル1のカラム, テーブル2のカラム, COUNT(テーブル3),
                                   
                              FROM テーブル1
                              LEFT JOIN テーブル2
                              ON テーブル1.なんとかid = テーブル2.なんとかid
                              LEFT JOIN テーブル3
                              ON テーブル2.なんとかID = テーブル3.なんとかid
                              WHERE テーブル3.delf=0

で、調べてこんな感じで書いたらいけた。関数内で条件指定できるらしい。OR NULLは必須。

select COUNT(テーブル3.delf=0 OR NULL) from テーブル名

参考

https://style.potepan.com/articles/20751.html

(そういえば論理削除はフラグよりもステータスで扱った方がいいみたいな話聞いたけど結局どうなんだろう)

fukabori.fm

AWS EKSの講座受けてみたが複雑なので内容整理

この間UdemyでMac上にminikubeの環境を構築してkubenetesを学ぶ講座を受けたが、その続きでローカルではなくAWS EKS上で本番運用っぽく動かしてみる講座があったので受講してみた。

米シリコンバレーエンジニア監修!AWS EKS KubernetesハンズオンBest Practices (2022) | Udemy

講座聴きながら一通り動かしてみたけど結構複雑で正直理解しきれてない感がある。。ので、講座についてた資料を見返し理解が曖昧な点を掘り下げながら整理してみる。

コマンドは大体講座の添付資料の通りだけど元はregionがus-west-2だったのでap-northeast-1に変更している。

EKSのセットアップ

前回minikubeの時にも使ったkubectlに加えてEKS操作用のeksctlとAWS CLI(awscli)を使ってターミナルからコマンドでEKS Clusterを立ち上げる。ただこれは学習目的で「本番環境ではTerraformやCloudFormationといったIaCを推奨」とある。

AWSの設定とかCLIのローカルへのインストールを済ませた後以下コマンド実行するとEKSのクラスタが作成される。

eksctl create cluster \
    --name eks-from-eksctl \
    --version 1.16 \
    --region ap-northeast-1 \
    --nodegroup-name workers \
    --node-type t3.medium \
    --nodes 2 \
    --nodes-min 1 \
    --nodes-max 4 \
    --ssh-access \
    --ssh-public-key ~/.ssh/eks_worker_nodes_demo.pem.pub \
    --managed

ここでいうnodeっていうのは前回の講座でも出たけどKubenetesの中のサーバの実態を表す概念みたいなものでAWS上で言うとEC2 or Fagateのホストに当たる。そういえば今回の講座ではworker nodeはEC2を使ったのでFagateの場合はどうするのかは別途調べないとかもな。。

nodeの他はname spaceとかversion指定とかsshの認証周りのオプションつけてる感じ。

このコマンド打った後にAWS管理画面開くとEKSやそのワーカノードのEC2がちゃんと作られていた。

あとクラスタ立ち上げっぱなしだとworker nodeのEC2の料金に加えてEKSを使うための料金も取られるので使わない時はマメにクラスタ落とした。

eksctl delete cluster --name eks-from-eksctl --region us-west-2

Helm Chartについて

Kubenetesは結構な量のyamlファイルの作成が必要なんだけど全部手で書くのは辛いのでHelmというツールで作成する。まあ、Kubenetesのパッケージマネージャだと思っておけば良さそうで調べた感じ現在のデファクトスタンダードみたい。

nginx用のyamlを作りたければこんな感じ。

helm search repo nginx
helm search repo bitnami/nginx
helm install nginx bitnami/nginx

このコマンドでConfigMap、Service、Deploymentとnginx環境構築に必要なリソースのyamlが用意される。 helmはstatausとかhistoryとかいろんな便利コマンドが用意されている。

Kuberenetesダッシュボードについて

Kubenetesクラスタの状態を監視するダッシュボードが紹介されている。 GitHub - kubernetes/dashboard: General-purpose web UI for Kubernetes clusters

kubectlでdashboardのリソースを作る →ダッシュボードにログインするためのトークン(パスワードみたいなもの)をSecretリソースから取得 →kubenetesクラスタへのプロキシを作ってブラウザからlocalhost:8001にアクセスするとクラスタの状態を監視できる。

  • kubectlでdashboardのリソースを作るコマンド
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.3.6/components.yaml

namespace、serviceaccount、service、secret、configmap、role、clusterrole、rolebindingなどができきてたけどぶっちゃけまだそれぞれのリソースの役割を理解できておらず講座でもそこまでそこは深追いはしてないのでここは別途調べないとかな。。

kubectlでsecretを取得→結果がbase64エンコードされてるのでデコードする。これをダッシュボードのログイン画面に貼るとログインできる。

kubectl get secrets  kubernetes-dashboard-token-t9z22 -n kubernetes-dashboard --output yaml
echo "{出てきたパスワード}" | base64 -D
  • プロキシ作成
kubectl proxy

サンプルアプリのデプロイ

「guestbook」と言うPHP+Redisでできたサンプルアプリをkubenetesで動かすためのjsonが公開されているのでそれをEKS上で動かした。

guestbookはkubenetes本体のgithubレポジトリの中で管理されている。

examples/guestbook-go/README.md at master · kubernetes/examples · GitHub

web上で公開されているjsonをkubectlで直接読んでクラスタ構築する感じ。

kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-master-controller.json

kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-master-service.json
kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/guestbook-controller.json

kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/guestbook-service.json

指定しているリソースはservice(Podのラッパー)とReplicationController(レプリカ数指定するやつ)

以下のコマンドで公開したサービスのAWS上のロードバランサを取得できる

kubectl  get svc guestbook

Networking: Ingress ControllerでPodを外部に公開

kubectl get service guestbook

と打つとレイヤ4のロードバランサが使われていることを確認できる。ただ本当はレイヤ7を使いたい。

helmでレイヤ7のロードバランサをinstall

kubectl create namespace nginx-ingress-controller

helm install nginx-ingress-controller stable/nginx-ingress -n nginx-ingress-controller

これを使うとnginxがレイヤ7のロードバランスをしてくれる。

ただこのやり方は問題があって本番環境で動かすならIstioを別途学んだ方がいいらしい。クラスタ内の認証が面倒でサービス間の認証のたびにサーバに負担がかかるって感じ?↓

NginxやAWS ALB Ingress Controllerは共にSSL terminationに対応しているが、バックエンドのアプリ間のTLSはアプリ次第となっています。つまり 1) アプリ間でTLS certを提供し、2) CPUリソースを消費するSSL handshakesを行う必要があります。

もしIstioを使うと、アプリのコードを一切変更せずに、クラスター内でデフォルトでSSLを可能にし、且つService Mesh機能であるCanaryリリースやFault Injectionなどもできるようになります。

Security: AWS Userの認証 (aws-iam-authenticator) と 認可(RBAC: Role Based Access Control)

AWS上でKubenetesを動かしているわけなのでAWSのIAMで認証されたAWSユーザーがK8sユーザーとして存在するか認可する(ややこしい)

kubectl get configmap aws-auth -n kube-system -o yaml

とコマンドを打つとAWSのユーザがkubenetesのユーザ、ユーザグループにマップされていることを確認できる。

IAMユーザに権限を与える場合(ルートユーザとして追加)Kubenetesのconfigmapに新しいAWS IAM userのARN(Amazonリソースネーム)を追加する。

kubectl edit -n kube-system configmap/aws-auth
mapUsers: |
    - userarn: arn:aws:iam::111122223333:user/eks-viewer   # AWS IAM user
      username: this-aws-iam-user-name-will-have-root-access
      groups:
      - system:masters  # K8s User Group

このAWS IAM userはsystem:mastersというK8sユーザーグループにバインドされている

ClusterRoleBinding

これはkebenetesの世界の話でkubenetesユーザにRoleを割り当てる。リソースの1種なのでyamlで定義してkubectlでapplyできる。

kubectl create clusterrolebinding system:viewer \
    --clusterrole=view \
    --group=system:viewer \
    --dry-run -o yaml > clusterrolebinding_system_viewer.yaml

Monitoring: PrometheusとGrafana

監視ツールの話。Prometheusはサーバインフラ監視ツール、Grafanaはログ・データの可視化ツールでどっちも別にkubenetes専用のものではないがkubenetes環境でも使えるって感じ。

どっちもHelmからinstallできる。Helmって監視ツールもインストールできるのか。。

# まずはnamespaceを作成
kubectl create namespace prometheus

helm install prometheus stable/prometheus \
    --namespace prometheus \
    --set alertmanager.persistentVolume.storageClass="gp2" \
    --set server.persistentVolume.storageClass="gp2"
kubectl create namespace grafana

helm install grafana stable/grafana \
    --namespace grafana \
    --set persistence.storageClassName="gp2" \
    --set persistence.enabled=true \
    --set adminPassword='EKS!sAWSome' \
    --set datasources."datasources\.yaml".apiVersion=1 \
    --set datasources."datasources\.yaml".datasources[0].name=Prometheus \
    --set datasources."datasources\.yaml".datasources[0].type=prometheus \
    --set datasources."datasources\.yaml".datasources[0].url=http://prometheus-server.prometheus.svc.cluster.local \
    --set datasources."datasources\.yaml".datasources[0].access=proxy \
    --set datasources."datasources\.yaml".datasources[0].isDefault=true \
    --set service.type=ClusterIP

(ベストプラクティス) Security: IRSA(IAM Role for Service Account)を使い、PodレベルのAWSへの認可を設定

PodにService Accountを指定してPodにトークンを持たせてそこからAWSapiサーバにつないでPod単位の認証かけられる。その流れざっくり噛み砕くとこんな感じ↓一つ一つは普段触ってるWebアプリの認証の仕組みと変わらないんだけど手順が多くて複雑だな。

Podを起動する時にAWSのAPIサーバにyamlを送る
→APIサーバのwebhookがk8sのService AccountとそのService AccountにIAM Role ARNがあることを確認する
→webhookがService Accountのannotationを元にpodに環境変数を設定
→Service AccountがOIDC(OpenID Connect)経由でAWS IAMから認証
→JWT トークンをOIDCから受け取ってPodの環境変数(AWS_WEB_IDENTITY_TOKEN_FILE)に保存
→以降PodでAWS CLIコマンドを使うと環境変数のトークンを使ってAWS IAM roleをAssumeする。

実際の構築コマンド

AWSでIODCを作成する必要あり(このハンズオンではeksctl create clusterを使ってclusterを作った際に作られているので未実施) 確認コマンドは略

aws iam create-open-id-connect-provider \
          --url $ISSUER_URL \
          --thumbprint-list $ROOT_CA_FINGERPRINT \
          --client-id-list sts.amazonaws.com

OIDC providerをEKSクラスターにリンク

eksctl utils associate-iam-oidc-provider \
            --region=us-west-2 \
            --cluster=eks-from-eksctl \
            --approve

管理画面からIAMを作成してから以下コマンド実行 Service accountを作成 →IAM role ARNをAnnotationに追加

eksctl create iamserviceaccount \
                --name irsa-service-account \
                --namespace default \
                --cluster eks-from-eksctl \
                --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
                --approve \
                --region ap-northeast-1

EC2 instance profileにアクセスが可能なのでブロック

podからEC2のインスタンスメタデータにアクセスできてしまうのでそこも塞がないとダメらしい

以下講義資料のコマンド。ec2に入ってiptablesコマンド打ってファイヤウォール設定するのか。これよく気づくな。

EKSワーカーノードにSSH

eval $(ssh-agent)
ssh-add -k ~/.ssh/
ssh -A ec2-user@192.168.20.213

iptablesコマンドを実行

yum install -y iptables-services
iptables --insert FORWARD 1 --in-interface eni+ --destination 169.254.169.254/32 --jump DROP
iptables-save | tee /etc/sysconfig/iptables 
systemctl enable --now iptables

11. (ベストプラクティス) Scaling: Cluster Autoscaler (CA)

Clusterのスケーリングの話

1.AWS ASGのタグを追加(AWGはAuto Scaling Groupのこと) →AWS管理画面からEC2→Auto Scaling Groupを開いてそこで設定する(クラスタ作成時に勝手に作られているので実際は作成不要。管理画面開くと確認できる)

2.cluster autoscalerへ認可(Authorization)のIAM パミッションをIRSA (Podレベル)かEC2 Instance Profile(ノードレベル)で追加

手順としてはIAMポリシーを管理画面から作成→eksctlでService AccountとIAMロールを作成の流れ

eksctl create iamserviceaccount \
                --name cluster-autoscaler-aws-cluster-autoscaler \
                --namespace kube-system \
                --cluster eks-from-eksctl \
                --attach-policy-arn arn:aws:iam::266981300450:policy/EKSFromEksctlClusterAutoscaler \
                --approve \
                --region ap-northeast-1

3.cluster-autoscalerをHelm ChartとしてEKS clusterにインストール

Region、ServiceAccount名、クラスタ名を記載したyamlを元にしてHelmでcluster-autoscalerをインストールする。これでAuto Scalingの設定が完了する。

awsRegion: ap-northeast-1

rbac:
  create: true # ref: https://github.com/kubernetes/autoscaler/issues/1507
  serviceAccount:
    create: false # because Service account and IAM role already created by `eksctl create iamserviceaccount` s
    name: cluster-autoscaler-aws-cluster-autoscaler

autoDiscovery:
  clusterName: eks-from-eksctl
  enabled: true

(ベストプラクティス) Scaling: Horizontal Pod Autoscaler (HPA、水平オートスケーリング)

Podの数を水平にスケールする

cluster-autoscalerはHelmで入れないといけなかったけどこっちはKubenetesのネイティブで入っているのでインストールは不要。

Metrics Serverは必要(ダッシュボード作る時に入れたやつ)

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.3.6/components.yaml

HPAリソースのyamlを作成してkubectlでapplyすればいいらしい。まあ見たまんまでCPUの状態見て最大数までPodを増やしてくれる。

$ kubectl autoscale deployment test-hpa \
    --min 1 \
    --max 7 \
    --cpu-percent=80 \
    --namespace default \
    --dry-run \
    -o yaml > hpa.yaml

$ kubectl apply -f hpa.yaml
$ kubectl get hpa 

感想

認証とかAutoScalingとかAWSのサービスとKubenetesの機能が複雑につながってる感じがしてなかなか辛そうだな。今回は学習用にコマンド打って一つ一つ設定してったけど、実際に使う時には一度KubenetesのマニフェストファイルとTerraformかCloudFormationを作りきってしまえばインフラの状態コードで把握できるし勝手にスケーリングもしてくれるんで今回みたいにいちいちコマンド打つのとは違った快適な感じになるのかな?

しかし学習コスト高いな。。昔Oracle触ってた時にこれOS学ぶのと同じくらい大変じゃね。。って思ってたけど同じような巨大さを感じる。

この講座だけではまだ本番で使うには不十分でIstioやインフラのコード化の知識やCI/CDも学ばないといけないみたい。しかもまだ開発活発みたいなんで新しいAPIのキャッチアップも必要なんだろうな。

Rails+Postgresql環境でauto incrementなカラムを作る

RailsPostgresql環境で主キーではないauto increment(Postgresqlではsequenceというらしい)な属性をもつカラムを作る必要があってやり方迷った。で、結局こうした。

Migration内でで使えそうなRailsAPIが見つからなかったので(こんなことそんなにやる場面なさそうだし)executeで直接実行してしまった。

sequenceをcreate→テーブルをcreate→sequenceをテーブルに付与という処理が必要っぽい。

今年度入ってからしばらくPostgresqlだけどその前はMySQL、その前はOracleだったんでなんか毎回軽く戸惑うな。

class Add{カラム名}To{テーブル名} < ActiveRecord::Migration
  def up
    add_column :{テーブル名}, :{カラム名}, :integer, comment: "hogehoge"
    execute <<-ENDE
      create sequence {テーブル名}_{カラム名}_seq;
      alter table {テーブル名} alter {カラム名} set default nextval('{テーブル名}_{カラム名}_seq');
      alter sequence {テーブル名}_{カラム名}_seq owned by {テーブル名}.{カラム名};
    ENDE
  end

  def down
    remove_column :{テーブル名}, :{カラム名}, :integer, comment: "hogehoge"
  end
end