フリーランスエンジニアのブログ

Elixir,Kubernetes,Terraformをいじっているエンジニアのブログ。メモ帳

RubyプロダクトをElixirプロダクトへリプレイスする作業を自動化しよう① fukuoka.ex Elixir/Phoenix Advent Calendar 2020【5分で読める】

この投稿は『fukuoka.ex Elixir/Phoenix Advent Calendar 2020』の19日目の記事です。
qiita.com


また、この投稿は、先日のElixir Digitalization Implementors #3 でLT発表させてもらった内容と一部重複しています。
fukuokaex.connpass.com

Schemaに関わる実装は自動化しよう。

RubyプロダクトのSchemaを把握してElixirで実装する作業って自動化できるのでは?

そう考えてこれを作りました。

github.com
f:id:ruby-deve:20201219225313p:plain

解説

前述のrtoeは既存のRubyプロダクトのmigrationファイルを読み込み、
ElixirプロダクトでSchemaを生成するコマンドを生成するものです。

一部抜粋して解説します。
基本的にこんなことをやっています。

  1. Rubyプロダクトのmigrationファイルを読み込む
  2. 読み込むんだ下記の文字列を加工

これがメインとなるメソッドです。

defmodule Mix.Tasks.Schema do
  @moduledoc "Printed when the user requests `mix schema migration_file_path`"
  @shortdoc "Migration file argument"

  use Mix.Task

  @impl Mix.Task
  def run(file) do
    {:ok, contents} = File.read(file)

    lines = String.split(contents, "\n")

    table =
      lines
      |> get_table_name

    class =
      lines
      |> get_class_name

    columns =
      lines
      |> get_columns

    elements = [
      "mix phx.gen.schema Context.#{class}",
      "context_#{table}",
      columns
    ]

    Mix.shell().info(Enum.join(elements, " "))
  end

  defp get_table_name(lines) do
    line =
      lines
      |> line_containing_word("create_table")

    case Regex.named_captures(~r/^create_table :(?<tn>.*) do |t|$/, "#{line}") do
      nil ->
        ""

      %{"tn" => table_name} ->
        table_name
    end
  end

  defp get_class_name(lines) do
    line =
      lines
      |> line_containing_word("class Create")

    case Regex.named_captures(~r/^class Create(?<cn>.*)s < ActiveRecord::Migration$/, "#{line}") do
      nil ->
        ""

      %{"cn" => class_name} ->
        class_name
    end
  end

  defp get_columns(lines) do
    lines
    |> Enum.filter(&is_column(String.trim("#{&1}")))
    |> Enum.map(fn str -> String.trim(str) end)
    |> Enum.map(fn str -> get_column_type(str) end)
  end

  defp get_column_type(str) do
    lc = String.split(str, ", ")
    [head | _] = lc

    case Regex.named_captures(~r/^t.(?<type>.*) :(?<col>.*)$/, "#{head}") do
      nil ->
        ""

      %{"type" => type, "col" => col} ->
        "#{col}:#{type} "
    end
  end

  defp is_column(line) do
    case Regex.named_captures(~r/^t.*$/, "#{line}") do
      nil ->
        false

      %{} ->
        true
    end
  end

  def line_containing_word(lines, word) do
    lines
    |> Enum.find(&String.contains?("#{&1}", word))
    |> String.trim()
  end
end

最後に適切なmix phx.gen.schemaコマンドを生成してくれます。

実際に

プロジェクトでも使っていて、
手作業でElixirのSchemaを実装する場合に比べてカラム名や型のミスもなくなりました。

今後

rtoeに関して、Elixir Digitalization Implementors #3 で頂いた下記のフィードバックも今後やりたいです。

  • CLI化 →releaseしました。(2020/12/28更新)

github.com

  • ディレクトリを指定して全てのRubyプロダクトのmigrationファイルを読み込み、全てのmix gen.schemaコマンドを生成する

また、Elixirへのリプレイスに関してどんなプロジェクトでも行う共通の工程があると思うのですが、できればそこを自動化していきたいです。


ちょっと短めですが、失礼いたします。

ここ最近やっていたインフラ

Elixir開発を主軸としている企業でのインフラ

* GCE上でのMySQL構築とレプリケーション
* ↑のバックアップ自動化、(ディスクを増設し定時にrsyncしてSlack通知)

大企業のセキュリティしっかり目の独立した検証環境を誰でもすぐ構築できるようにTerraformで開発

* Amazon SSMを使ってインスタンスへの接続
* 設計と実装
* Terraformのバージョンアップ 0.12系->0.13系
* AWS SSM, VPC ~ RDS

Amazon Linux2でGitlab Runnerの構築・設定

(途中で引き継ぎました。)

1000人のエンジニアが使うリードレプリカ構成のサービス構築

* 負荷分散、kubernetesyamlをいじって調整
* Helm Chart化。
* 構築を自動化したシェルスクリプトbash)の開発
* AWS EKS, Route53, EC2, ELB, EFS, ExternalDNS

ベンチャーの新規プロダクト・PoCのリリース・構築

(お手伝いレベルになります。)
* ヒアリング手伝い
* 大企業のAWS上に構築お手伝い
* Amazon Linux2
* プロダクトはGo, GraphQL, gqlgen, TypeScriptでした。

pythonで書かれたバックアップmoduleをアップデート・メンテナンス

* Slack通知
* fabric
* AWS Lambda, Cloud Watch, EBS, EC2, S3
* 監視など

RubyのプロダクトをさくらのレンタルサーバーからGCPへ移設

* リードレプリカ(Master-Slave)構成に変更

大企業のサーバーが攻撃された時の対応

* log調査
* セキュリティ見直し

メディア関連

* サーバーをXserverからXserverに移設
* 高速化

特定業界の新規事業(インフラじゃ無いけど

* 事業計画から一緒に作ってPoC作成
* 実装をエンジニア1名に、フロントをデザイナー1名にお願いする感じに。
* Vagrant, docker-compose, mutagen(仮想環境とのファイル同期)を使用。Ruby

kubernetesのyamlに記載すべきapiVersion

kubectl explain 'Object'

例えば
kubectl explain deployment
が便利

記載すべきはapps/v1 だと分かる。

$ kubectl explain deployment
KIND:     Deployment
VERSION:  apps/v1

DESCRIPTION:
     Deployment enables declarative updates for Pods and ReplicaSets.

FIELDS:
   apiVersion	<string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

   kind	<string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

   metadata	<Object>
     Standard object metadata.

   spec	<Object>
     Specification of the desired behavior of the Deployment.

   status	<Object>
     Most recently observed status of the Deployment.

EKSでconfigにクラスタを追加しクラスタを切り替えれるように。

いつも忘れるのでメモ。

kubectlのconfigにクラスタを追加

aws eks update-kubeconfig --name <クラスタ名>

kubectl でのコンテキスト一覧および切り替え
一覧

$ kubectl config get-contexts

切り替え

$ kubectl config use-context $CLUSTER_NAME

Currentの表示

$ kubectl config current-context

【Kubernetes】ローカルにクラスタを作成してNginxをLoadBalancerタイプでデプロイ

こんにちは。

この記事は Kubernetes Advent Calendar 2019 の2日目の記事になります。
qiita.com

前提

  • kubectlコマンドが使える。
  • Minikubeが利用できる環境にある。

クラスタの用意

ローカルにクラスタを用意。

$ minikube start

確認。

$ minikube status
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

Dashboardを確認する。

$ minikube dashboard

Deploymentの作成

deployment.yaml の作成

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.9.1
        ports:
        - containerPort: 80

apply する

$ kubectl apply -f ./deployment.yaml

Serviceの作成

service.yaml の作成

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
    - name: http
      port: 8080
      targetPort: 80

apply する

$ kubectl apply -f ./service.yaml

確認

MinikubeのDashboardで見てみる。

f:id:ruby-deve:20191124023957p:plain
dashboard_pods

Podが3つ。

サービスとURLの確認

$ kubectl get services
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP      10.96.0.1       <none>        443/TCP          87m
nginx        LoadBalancer   10.101.66.247   <pending>     8080:31695/TCP   110s

$ minikube service nginx --url
http://192.168.64.6:31695

minikube service nginx --url
で表示されたURLにブラウザでアクセスする。
この場合だと、http://192.168.64.6:31695にアクセスするとNginxの表示が確認できる。

f:id:ruby-deve:20191124022154p:plain
Nginx