fbpx

SchemaHeroでDBのschema migrationしてみた

はじめに

DBのschema管理といえばプロフェッショナルがDDL書いて流すレベルの高い作業の一つだという偏見を個人的に持っています。

私はDB分野の人ではないので、自分でやるとしたら緊張感に押しつぶされて誤ったDDLを書いてしまいそうです。

そこで今回はDDLよりいくらか簡単なYAMLでschemaを定義してshema管理を実現させるCloud Native Computing Foundation Sandbox ProjectsのOSSであるSchemaHeroをご紹介したいと思います。

https://schemahero.io/

本記事の準拠バージョン

本記事ではSchemaHero v0.17.5を利用します。

SchemaHeroをざっくり紹介

SchemaHeroは宣言的にDBのschema管理を実現するOSSで、平たく言えばYAMLで定義した状態のschemaを実現してくれるOSSです。(Ansibleのschema版とイメージすると分かりやすいかもしれません)

Cloud Native Computing Foundation Sandbox ProjectsのOSSでライセンスはApache-2.0 licenseです。

Kubernetes上で動作します。

ホームページ: https://schemahero.io/

GitHubリポジトリ:https://github.com/schemahero/schemahero

公式ドキュメント:https://schemahero.io/docs/

SchemaHeroが使えるDBサーバ・ライブラリ

SchemaHero v0.17.5では以下のDBがサポートされています。

  • Postgresql 9.5 - 15.1
  • Mysql 5.6 - 8.0
  • Cockroachdb v19.2 - v22.1
  • Cassandra 3.x
  • SQLite 3.x

最新の対応状況、今後の対応ロードマップはこちらを参照してください。

https://schemahero.io/databases/

ORMとの比較

ORMとの比較はSchemaHero公式でも言及されています。

https://schemahero.io/learn/comparisons/orm/

要点としては

  • SchemaHeroはschemaの管理、変更の追跡をより良い方法で行うためのもの
  • SchemaHeroはshcema migration toolでORMのようなquery builderの機能はない
  • 既にORMでschemaを管理できているのであればSchemaHeroを導入する理由はない

になります。

検証内容

チュートリアルの内容をたどり、SchemaHeroでschemaのデプロイ、変更を行う。

SchemaHeroの導入

SchemaHeroはクライアントソフトウェアからインストールする構成を取っています。

クライアントの導入

クライアントはkubectlのプラグインとして提供されていて、krew経由でインストールすることができます。

krewを導入していない場合は以下から先に導入しておいてください。

https://krew.sigs.k8s.io/

krewさえあれば導入はすぐにできます。

$ kubectl krew update
$ kubectl krew install schemahero

正常に導入されたかの確認はコマンドから

$ kubectl schemahero version
SchemaHero v0.17.5

といった形で確認できます。

SchemaHeroの導入

SchemaHero、SchemaHeroの利用するCRDの導入はクライアントから1コマンドで実行可能です。

$ kubectl schemahero install

以上です。

クライアントに依存せず、手動やGitリポジトリからYAMLを適用したい場合は

$ kubectl schemahero install --yaml

で同等のYAML定義を出力することができます。

導入結果はコマンドから

$ kubectl get pod -n schemahero-system 
NAME           READY   STATUS    RESTARTS   AGE
schemahero-0   1/1     Running   0          12m

$ kubectl api-resources | grep schemahero.io
databases                                      databases.schemahero.io/v1alpha4   true         Database
migrations                                     schemas.schemahero.io/v1alpha4     true         Migration
tables                                         schemas.schemahero.io/v1alpha4     true         Table
views                                          schemas.schemahero.io/v1alpha4     true         View

といった形で確認できます。

チュートリアル実施用のDBとしてPostgreSQL podをデプロイ

新たにネームスペースを作成し、そこにチュートリアルで利用するDBとしてPostgreSQLをデプロイします。

$ kubectl create ns schemahero-tutorial
$ kubectl apply -n schemahero-tutorial -f https://raw.githubusercontent.com/schemahero/schemahero/main/examples/tutorial/postgresql/postgresql-11.8.0.yaml

実際にどのような定義がデプロイされるかは

https://github.com/schemahero/schemahero/blob/main/examples/tutorial/postgresql/postgresql-11.8.0.yaml

を参照してください。

セットアップが正常かの判断はpodがrunningになっている事を確認した後、

$ kubectl exec -it -n schemahero-tutorial postgresql-0 -- psql -U airlinedb-user -d airlinedb

コマンドでログイン(パスワードはpassword)して接続できるか確認してください。

ログイン後は\dtコマンドで初期状態ではschemaが存在しないことが確認できます。

airlinedb=> \dt
Did not find any relations.

SchemaHeroをDBに接続する

実のところ、まだSchemaHeroとDBは接続されていません。

接続先と認証情報を与えてSchemaHeroをDBに接続させます。

  • airline-db.yamlという名前のファイルを以下の内容で作成する
apiVersion: databases.schemahero.io/v1alpha4
kind: Database
metadata:
  name: airlinedb
  namespace: schemahero-tutorial
spec:
  connection:
    postgres: # 接続先の種類にPostgreSQLサーバを指定
      uri: # uri形式で接続先、認証情報を指定
        valueFrom:
          secretKeyRef:
            name: postgresql
            key: uri
  • このairline-db.yamlファイルをkubernetesに適用する
$ kubectl apply -f ./airline-db.yaml

airline-db.yamlの中身の解説ですが、airlinedbという名前のDatabaseカスタムリソースでSchemaHeroにDBの接続情報を渡しています。

中身はspec.connection.postgresでPostgreSQLサーバである事を示し、その中のuriで接続文字列を指定しています。

接続文字列はsecretリソースを参照していて、参照結果はpostgresql://airlinedb-user:password@postgresql:5432/airlinedbといった文字列になっています。

これでDBに接続するための情報がSchemaHeroに渡り、SchemaHeroがDBに接続できます。

SchemaHeroでtableを作成する

SchemaHero経由でこちらのDDLで作成されるtableと等しいtableを作成します

CREATE TABLE airport
  (
    code char(4) not null primary key,
    name varchar(255)
  )

SchemaHeroでtableはTableカスタムリソースで定義します

具体的な手順としては

  • airport-table.yamlファイルを以下の内容で作成する
apiVersion: schemas.schemahero.io/v1alpha4
kind: Table
metadata:
  name: airport
  namespace: schemahero-tutorial
spec:
  database: airlinedb # airline-db.yamlで定義した名前に揃える
  name: airport # table名
  schema:
    postgres:
      primaryKey: [code] # primalyKeyのcolumnを指定
      columns: # columnの名前と型を定義
        - name: code
          type: char(4)
        - name: name
          type: varchar(255)
          constraints:
            notNull: true # not null制約を有効にする
  • 上記のtable定義を適用する
$ kubectl apply -f ./airport-table.yaml

これでtable定義が作成されました。

次に、このtable定義をどう実現するかをレビューします。

  • 承認待ちの変更を確認します。例として、ここではIDがeaa36efの変更があることがわかります。
$ kubectl schemahero get migrations -n schemahero-tutorial 
ID       DATABASE   TABLE    PLANNED  EXECUTED  APPROVED  REJECTED
eaa36ef  airlinedb  airport  13m    
  • 具体的にどのような内容なのか見ていきます
$ kubectl schemahero describe migration eaa36ef -n schemahero-tutorial

Migration Name: eaa36ef
Generated DDL Statement (generated at 2024-03-02T11:41:54+09:00): 
  create table "airport" ("code" character (4), "name" character varying (255) not null, primary key ("code"))

To apply this migration:
  kubectl schemahero -n schemahero-tutorial approve migration eaa36ef

To recalculate this migration against the current schema:
  kubectl schemahero -n schemahero-tutorial recalculate migration eaa36ef

To deny and cancel this migration:
  kubectl schemahero -n schemahero-tutorial reject migration eaa36ef

DDLとしてはcreate table "airport" ("code" character (4), "name" character varying (255) not null, primary key ("code"))が適用されるようです。

  • 変更を承認します。
$ kubectl schemahero -n schemahero-tutorial approve migration eaa36ef
Migration eaa36ef approved
  • ステータスが変わったか確認します。
$ kubectl schemahero get migrations -n schemahero-tutorial
ID       DATABASE   TABLE    PLANNED  EXECUTED  APPROVED  REJECTED
eaa36ef  airlinedb  airport  19m      52s       52s       
  • DB側のコマンドラインでtableが作成されているか確認します。
airlinedb=> \d+
                         List of relations
 Schema |  Name   | Type  |     Owner      |  Size   | Description 
--------+---------+-------+----------------+---------+-------------
 public | airport | table | airlinedb-user | 0 bytes | 
(1 row)
 
airlinedb=> \d+ airport
                                          Table "public.airport"
 Column |          Type          | Collation | Nullable | Default | Storage  | Stats target | Description 
--------+------------------------+-----------+----------+---------+----------+--------------+-------------
 code   | character(4)           |           | not null |         | extended |              | 
 name   | character varying(255) |           | not null |         | extended |              | 
Indexes:
    "airport_pkey" PRIMARY KEY, btree (code)

上記と同様の手順でschedule-table.yamlを作成し、schedule tableを作成します。

  • schedule-table.yamlファイルを以下の内容で作成する
apiVersion: schemas.schemahero.io/v1alpha4
kind: Table
metadata:
  name: schedule
  namespace: schemahero-tutorial
spec:
  database: airlinedb
  name: schedule
  schema:
    postgres:
      primaryKey: [flight_num]
      columns:
        - name: flight_num
          type: int
        - name: origin
          type: char(4)
          constraints:
            notNull: true
        - name: destination
          type: char(4)
          constraints:
            notNull: true
        - name: departure_time
          type: time
          constraints:
            notNull: true
        - name: arrival_time
          type: time
          constraints:
            notNull: true
  • schedule-table.yamlファイルを適用する
$ kubectl apply -f ./schedule-table.yaml
  • 変更をレビューする
$ kubectl schemahero get migrations -n schemahero-tutorial
ID       DATABASE   TABLE     PLANNED  EXECUTED  APPROVED  REJECTED
a9626a8  airlinedb  schedule  10s                          
eaa36ef  airlinedb  airport   30m      11m       11m       

$ kubectl schemahero describe migration a9626a8 -n schemahero-tutorial

Migration Name: a9626a8

Generated DDL Statement (generated at 2024-03-02T12:12:33+09:00): 
  create table "schedule" ("flight_num" integer, "origin" character (4) not null, "destination" character (4) not null, "departure_time" time not null, "arrival_time" time not null, primary key ("flight_num"))

To apply this migration:
  kubectl schemahero -n schemahero-tutorial approve migration a9626a8

To recalculate this migration against the current schema:
  kubectl schemahero -n schemahero-tutorial recalculate migration a9626a8

To deny and cancel this migration:
  kubectl schemahero -n schemahero-tutorial reject migration a9626a8
  • 変更を承認する
$ kubectl schemahero -n schemahero-tutorial approve migration a9626a8
Migration a9626a8 approved
  • DB側でtableが作成されているか確認する
airlinedb=> \d+ schedule
                                             Table "public.schedule"
     Column     |          Type          | Collation | Nullable | Default | Storage  | Stats target | Description 
----------------+------------------------+-----------+----------+---------+----------+--------------+-------------
 flight_num     | integer                |           | not null |         | plain    |              | 
 origin         | character(4)           |           | not null |         | extended |              | 
 destination    | character(4)           |           | not null |         | extended |              | 
 departure_time | time without time zone |           | not null |         | plain    |              | 
 arrival_time   | time without time zone |           | not null |         | plain    |              | 
Indexes:
    "schedule_pkey" PRIMARY KEY, btree (flight_num)

これでSchemaHeroを通してtableを作成する事ができました!

Table定義の変更

先程作成したschedule tableのcolumnを変更します。

変更内容は

  • departure_timeをnullableに
  • arrival_timeをnullableに
  • intが入るduration columnを追加する

になります。手順は

  • 先ほど作成したschedule-table.yamlファイルを以下の内容に変更する
apiVersion: schemas.schemahero.io/v1alpha4
kind: Table
metadata:
  name: schedule
  namespace: schemahero-tutorial
spec:
  database: airlinedb
  name: schedule
  schema:
    postgres:
      primaryKey: [flight_num]
      columns:
        - name: flight_num
          type: int
        - name: origin
          type: char(4)
          constraints:
            notNull: true
        - name: destination
          type: char(4)
          constraints:
            notNull: true
        - name: departure_time # constraintsを取り除く
          type: time
        - name: arrival_time # constraintsを取り除く
          type: time
        - name: duration # 追加分
          type: int
  • schedule-table.yamlファイルを適用する
$ kubectl apply -f ./schedule-table.yaml
  • 変更をレビューする。
$ kubectl schemahero get migrations -n schemahero-tutorial
ID       DATABASE   TABLE     PLANNED  EXECUTED  APPROVED  REJECTED
a9626a8  airlinedb  schedule  12m      9m37s     9m37s     
eaa36ef  airlinedb  airport   43m      24m       24m       
fa32022  airlinedb  schedule  4s                           

$ kubectl schemahero describe migration fa32022 -n schemahero-tutorial


Migration Name: fa32022

Generated DDL Statement (generated at 2024-03-02T12:24:56+09:00): 
  alter table "schedule" alter column "departure_time" type time, alter column "departure_time" drop not null;
alter table "schedule" alter column "arrival_time" type time, alter column "arrival_time" drop not null;
alter table "schedule" add column "duration" integer

To apply this migration:
  kubectl schemahero -n schemahero-tutorial approve migration fa32022

To recalculate this migration against the current schema:
  kubectl schemahero -n schemahero-tutorial recalculate migration fa32022

To deny and cancel this migration:
  kubectl schemahero -n schemahero-tutorial reject migration fa32022

alter tableが実行され、table定義を変更する内容です。

  • 変更を承認する
$ kubectl schemahero -n schemahero-tutorial approve migration fa32022
Migration fa32022 approved
  • DB側で変更が適用されているか確認する
airlinedb=> \d+ schedule
                                             Table "public.schedule"
     Column     |          Type          | Collation | Nullable | Default | Storage  | Stats target | Description 
----------------+------------------------+-----------+----------+---------+----------+--------------+-------------
 flight_num     | integer                |           | not null |         | plain    |              | 
 origin         | character(4)           |           | not null |         | extended |              | 
 destination    | character(4)           |           | not null |         | extended |              | 
 departure_time | time without time zone |           |          |         | plain    |              | 
 arrival_time   | time without time zone |           |          |         | plain    |              | 
 duration       | integer                |           |          |         | plain    |              | 
Indexes:
    "schedule_pkey" PRIMARY KEY, btree (flight_num)

これでtableの定義を変えることができました。table作成の時と手順が変わらないので覚えやすいですね。

外部キー制約の追加

先程のschedule tableに外部キー制約を追加します。

具体的には以下を実現します。

  • airport tableのcode columnをschedule tableのorigin columnの外部キーに指定する
  • airport tableのcode columnをschedule tableのdestination columnの外部キーに指定する

実現方法は先程と同じく、schedule-table.yamlの変更と適用になります。

  • schedule-table.yamlを以下の内容に変更する
apiVersion: schemas.schemahero.io/v1alpha4
kind: Table
metadata:
  name: schedule
  namespace: schemahero-tutorial
spec:
  database: airlinedb
  name: schedule
  schema:
    postgres:
      primaryKey: [flight_num]
      foreignKeys: # ここで外部キーを設定する
        - columns:
            - origin # 外部キー制約を適用するcolumn
          references: # 外部キーとして利用するtableのcolumnを指定する
            table: airport
            columns:
              - code
        - columns:
          - destination
          references:
            table: airport
            columns:
              - code
      columns:
        - name: flight_num
          type: int
        - name: origin
          type: char(4)
          constraints:
            notNull: true
        - name: destination
          type: char(4)
          constraints:
            notNull: true
        - name: departure_time
          type: time
        - name: arrival_time
          type: time
        - name: duration
          type: int
  • schedule-table.yamlファイルを適用する
$ kubectl apply -f ./schedule-table.yaml
  • 変更をレビューする
$ kubectl schemahero get migrations -n schemahero-tutorial
ID       DATABASE   TABLE     PLANNED  EXECUTED  APPROVED  REJECTED
a9626a8  airlinedb  schedule  27m      25m       25m       
b12d3fd  airlinedb  schedule  40s                          
eaa36ef  airlinedb  airport   58m      39m       39m       
fa32022  airlinedb  schedule  15m      13m       13m 

$ kubectl schemahero -n schemahero-tutorial describe migration b12d3fd

Migration Name: b12d3fd

Generated DDL Statement (generated at 2024-03-12T12:39:48+09:00): 
  alter table schedule add constraint schedule_origin_fkey foreign key ("origin") references "airport" ("code");
alter table schedule add constraint schedule_destination_fkey foreign key ("destination") references "airport" ("code")

To apply this migration:
  kubectl schemahero -n schemahero-tutorial approve migration b12d3fd

To recalculate this migration against the current schema:
  kubectl schemahero -n schemahero-tutorial recalculate migration b12d3fd

To deny and cancel this migration:
  kubectl schemahero -n schemahero-tutorial reject migration b12d3fd

alter tableが実行され、外部キー制約が適用される内容になっています。

  • 変更を承認する
$ kubectl schemahero -n schemahero-tutorial approve migration b12d3fd
Migration b12d3fd approved
  • 変更が適用されているかDB側で確認する
airlinedb=> \d+ schedule
                                             Table "public.schedule"
     Column     |          Type          | Collation | Nullable | Default | Storage  | Stats target | Description 
----------------+------------------------+-----------+----------+---------+----------+--------------+-------------
 flight_num     | integer                |           | not null |         | plain    |              | 
 origin         | character(4)           |           | not null |         | extended |              | 
 destination    | character(4)           |           | not null |         | extended |              | 
 departure_time | time without time zone |           |          |         | plain    |              | 
 arrival_time   | time without time zone |           |          |         | plain    |              | 
 duration       | integer                |           |          |         | plain    |              | 
Indexes:
    "schedule_pkey" PRIMARY KEY, btree (flight_num)
Foreign-key constraints:
    "schedule_destination_fkey" FOREIGN KEY (destination) REFERENCES airport(code)
    "schedule_origin_fkey" FOREIGN KEY (origin) REFERENCES airport(code)

これで外部キー制約が追加できました。

最後に

DDLが苦手な自分でもSchemaHeroでならschema管理できそうな気がしてきました。

各ORMの書き方や方言を覚えなくても慣れ親しんだYAMLで定義できるところは好印象でした。(構造は覚える/調べる必要がありますが...)

DBの知識は必要ですが、あまり構造を覚えなくとも他人の書いたYAMLをレビューすることも可能なのではないでしょうか。

Author

色々やらせてもらっている系エンジニア。
Arch Linuxユーザー。

菅野 洋信の記事一覧

新規CTA