nokoのブログ

こちらは暫定のメモ置き場ですので悪しからず

Terraformのディレクトリ構成について考えたこと

前提

  • 小規模というほどではないが決して大規模ではない環境を組むときにちょうどいいTerraformのディレクトリ構成について悩んだときのメモです。
    • 環境ごとにVPCとかのネットワークを切って、それぞれにEC2(ECS)+RDS+S3とかとかを数台ずつ乗せていくくらい。

観点

  • [効率]環境ごとの差異を比較しやすいこと
    • dev→stg→prodとデプロイしていくとして、差分反映・確認がしやすいこと
  • [安定]安全にデプロイしやすいこと
    • 変更頻度や影響範囲を考慮

ディレクトリ構成

  • はじめに結論
├── env
│   └── stg01
│       |── network
│       |   └── main.tf
│       └── service-aaa
│           └── main.tf
└── modules
    └── vpc
        └── main.tf

ファイル例

  • env/network/stg01
#----------
# Terraform
#----------
terraform {
  required_version = "0.13.2"
  backend "s3" {
    bucket  = "system-stg01-deploy-tf-999999999"
    region  = "ap-northeast-1"
    key     = "tfstate"
    encrypt = true
  }
}

#----------
# Provider
#----------
provider "aws" {
  region = "ap-northeast-1"
}


#----------
# Remote State
#----------
data "terraform_remote_state" "XXX" {
  backend = "s3"

  config = {
    region = "ap-northeast-1"
    bucket = "system-stg01-deploy-tf-999999999"
    key    = "tfstate.XXX"
  }
}

#----------
# Local Values
#----------
locals {
  env = "stg01"
}

#----------
# Resource - Network
#----------
# vpc
module "vpc" {
  source = "../../modules/vpc"

  vpc_cidr_block = "192.168.10.0/24"
  vpc_tags_Name  = "system-${local.env}-vpc"
}

# public subnet
module "public-subnet01" {
  source = "../../modules/subnet"

  subnet_vpc_id                  = module.vpc.vpc_id
  subnet_cidr_block              = "192.168.10.0/26"
  subnet_map_public_ip_on_launch = "true"
  subnet_tags_Name               = "system-${local.env}-public-subnet01"
}

module "public-subnet02" {
  source = "../../modules/subnet"

  subnet_vpc_id                  = module.vpc.vpc_id
  subnet_cidr_block              = "192.168.1.64/26"
  subnet_map_public_ip_on_launch = "true"
  subnet_tags_Name               = "system-${local.env}-public-subnet02"
}
  • modules/vpc/main.tf
variable "vpc_cidr_block" {}
variable "vpc_tags_Name" {}

resource "aws_vpc" "this" {
  cidr_block           = var.vpc_cidr_block
  instance_tenancy     = "default"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = var.vpc_tags_Name
  }
}

output "vpc_id" {
  description = "The ID of the VPC"
  value       = concat(aws_vpc.this.*.id, [""])[0]
}

実装方針

  • モジュール切り出し+環境分離+コンポーネント分割
  • 変数の値は4箇所に集約(全て環境ファイルに)
    • 環境ファイルに直書き
    • 同一環境ファイルの別モジュールの値(output)
    • 環境ファイルのLocal Values
    • コンポーネントの値(outputをRemote Stateとして拾う)
  • 全てmain.tfに寄せる
    • 環境ファイルとmoduleファイル
    • variable.tfとか切り出さない

検討経緯

モジュール切り出し+環境分離+コンポーネント分割

  • 推奨されてそうなのでモジュール切り出しにして見た
  • 今後のことを考えて受け身が取れるように
  • モジュール切り出しは、ダイナミックとかロジック入れるときに役立つ?
  • ただ、ロジックは極力使わずベタ書きしている
    • EC2のcountとかあるが、tagのインデックスくらいならいいがEIPとか絡むとややこしくなる。だったら環境ファイル側でベタ書きする。
  • →が、環境ごとに下手書きで十分な気がする(モジュールを繰り返し利用したところで、記述量が減らない)
    • 結局、コードとは違う。設定値の羅列で、ロジックがないから。ベタ描きで十分 or 思い切ってWorkspace?
      • Workspaceも、比較しやすい?
  • moduleはあくまでパーツ。システム側の都合を考慮せず、疎結合に組み合わせられるように実装する
  • tf.stateの分割
    • 変更が多い/少ない
    • 非機能要件の違い

変数の値は4箇所に集約(全て環境ファイルに)

  • 同一環境ファイルの別モジュールの値(output)
module.vpc.vpc_id
  • 環境ファイルのLocal Values

    • 比較しやすくなるように、環境変数(dev, prodなど)はLocal Valuesとして一度だけ定義する
  • コンポーネントの値(outputをRemote Stateとして拾う)

    • 呼び出し先のmoduleではなく、呼び出し元でoutputとして定義する必要あり
data.terraform_remote_state.network.outputs.vpc_vpc_id

全てmain.tfに寄せる

  • 「観点」記載の通り、環境ごとの差分を比較しやすくしたいので、分けることにあまり意味がない
    • それぞれのファイルの量も少ない

参考

共通コードのディレクトリにはclassの定義が、 環境ごとのディレクトリにはclassのインスタンスを作る際に渡す実際の値が定義されていると イメージすると分かりやすいと思います。
# コンポーネント分割例
* network
  * vpc,subnet
* routing
  * route53,alb,acm
* services
  * ecsのサービス単位(ecs cluster,taskdifinition,service,それに付随するec2系リソース ecr pipeline系)
* opsserver
  * session manager,ec2
* events
  * やりたい処理単位(lambda cloudwatchEvents ここはApex+Terraform)
  * モニタリングとかもここに
* state machine
  * step function
* storage
  * s3
* datastore
  * elasticache,RDS
  * elasticache,RDSを別々に
* iam user
# コンポーネント分割例
* network
  * ネットワーク系の設定VPC、Subnet、NAT、Route Tables等の設定を管理
* securitygroup
  * セキュリティグループの管理
* iam
  * IAMの管理
* s3
  * S3回りの管理
* backend
  * DB等のデータストア関連のリソースを管理
* webservers
  * webサーバやALB等の管理
* opsservers
  * 運用系のサーバの管理

Terraformについて調べたことメモ

はじめに

  • Terraformまわりについて復習したときのメモ

メモ

ディレクトリ構成案(最小構成)

.
├── README.md
└── stg01
    ├── ec2.tf
    ├── provider.tf
    └── versions.tf

ファイル例

  • ec2.tf
resource "aws_instance" "<pj>-stg01-app-01" {
  ami           = "ami-XXXXX"
  instance_type = "t2.medium"
  count = 1
  ebs_block_device {
    device_name    = "/dev/sda1"
    volume_type = "gp2"
    volume_size = 20
  }
  tags = {
    Name = "<pj>-stg01-app-01"
    Project = "<pj>"
  }
  key_name = "XXXXX-key"
  vpc_security_group_ids = ["sg-XXXXX"]
  subnet_id = "subnet-XXXXX"
}
  • provider.tf
provider "aws" {
  region  = "ap-northeast-1"
}

Terraformインストール手順(@デプロイ用マシン)

  • Terraformインストール
wget https://releases.hashicorp.com/terraform/0.13.2/terraform_0.13.2_linux_amd64.zip
unzip terraform_0.13.2_linux_amd64.zip
# PATHが通っているディレクトリにコピー
sudo cp terraform /usr/local/bin/
terraform -version
  • AWS環境情報設定
aws configure
# ~/.bashrc に記載して、 source ~/.bashrc でも可
# export AWS_ACCESS_KEY_ID=***
# export AWS_SECRET_ACCESS_KEY=***

Terraform実行手順(@デプロイ用マシン)

cd terraform/stg01
terraform init
terraform plan
terraform apply
terraform show

補足

  • リソースを破棄するコマンド
# 注意!
terraform destroy

MLOps勉強会Tokyo(Online)#1参加レポート

はじめに

メモ

MLOpsコミュニティの発足にあたり

  • DataRobot シバタさん
  • アンケート
  • 可愛そうなモデルを根絶したい
  • MLOpsは新しい分野。何が求められているのかをディスカッションしたりもしたい。
    • MLOpsに必要とされているものとは

先駆者に学ぶ MLOpsの実際

  • システム開発に置いて、データサイエンティストがやらないこと全て
    • 環境整備・成果物管理
    • 本番データでのモデル推定
    • 推論結果のシステムへの組み込み
    • 効果モニタリング
  • Walmart
    • MLプロジェクトの60-80%がプロダクションにならずに頓挫している
    • 本番化する計画は明瞭か
      • -> 企業にとって利益が出る(意味がある)システムにまで組み込み切る必要がある
  • データサイエンティスト並にデータエンジニアの求人が多い
  • Netflix
    • データの探索 2w
    • プロトタイピング 6-8w
    • 本番化 12-14w
    • リリース後のモデル更新 8w
  • MLOps NYC2019注目セッションまとめがある
  • SageMakerを使っている
  • エラーハンドリングもデータエンジニアが実装
  • とにかくデータサイエンティストの負担を軽減してあげる
  • マルチクラウドのメリット
    • AWS + GCP
    • GCPのBQ, SageMakerを使いたかった
  • コーディングフローの整備までやっているか?
    • コーディングルールとかはレビュー

DiscoveryDataScienceMeetup(DsDS)#0参加レポート

はじめに

メモ

広告文自動生成プロダクトでDataflowを導入した話

  • ダイレクトコピーの自動生成
  • Cloud Runでマイクロサービスを組んでいる
  • 分析はCompute Engineでやっている
  • pandas on GCEだとデータ量的に辛い
    • データ整形1週間とか
  • →Dataflowを使っている
  • デバッグのために、最初はローカルで行うべき。runnerオプションの切り替えだけでいい。
  • 実行環境の切り分け→Makefileとか、Dockerで包んだり
  • Dataflowでは非Pypiパッケージのインストールがかなり面倒   * 運用で使用しているマイクロサービスAPIを流用
  • Cloud Functionだとpipパッケージのインストールはできる一方、apt-get系のインストールができないんですよねCloud RunだとDockerレベルで環境を構築できるので、apt-getが必要な形態素解析パッケージをいれるためにCloud Runを採用しています!

TensorflowのモデルをGCPでサービングしてきた話

  • 予測APIの提供
  • 入力は画像andテーブル
  • なぜGCPか?→BQのため
    • クラウドの列指向DBに比べて 機能が豊富・性能が高い・使いやすい

  • AI Platform
    • GCSにモデルをおくだけで、APIを提供してくれる
  • 「結局前処理サーバーは必要だった」
  • AI Platform Custom Prediction Routine
    • ツラミ多し
    • モデルの容量制限とか
    • 廃止が決まっている
  • →結局GKE
  • 現時点だと saved model と custom prediction routine 共に Pytorch は選べないようです!

SageMakerで試行錯誤する推論パイプライン

  • 歴史
    • 最初は生EC2だった
      • ステップが密結合になりがち、一番リソースを食う処理に合わせてインスタンス選択、ライブラリバージョンが固定し辛い(1枚のpythonファイルなので)
    • →SageMaker Batch TransforをAirflowからキック
      • AirflowはECS上に自前→結構辛い
    • →Batch Transfor
      • バッチと言いつつ…。
    • →Processingを使うように
    • AWS Batchを使っていいのでは?
    • →training jobで推論までやっちゃう。スポットインスタンスのため。
      • モデルの推論も
  • SageMakerは、今までベタ書きだったコードを書き換えないといけない
  • SageMakerのlocalモードはまだできないことがある *スポットインスタンスのために、airflowは東京リージョンなのにクロスリージョンでus-east-1を
  • リトライはairflowで実装
  • 一応awsのstep functionsも検討に上がりましたが、当時はあまり使いやすそうじゃなかったのでほぼ迷わずairflow

Cloud Composerで組む機械学習パイプライン

  • CloudComposerで前処理からデプロイまで
  • クラスタ管理やログ管理がマネージド
  • GKE Pod Operator
    • Composerとは別のクラスタのPod
    • 基本方針は、全部これで(pythonとかBQやらない)
      • pythonは環境汚染。Airflow自体との競合
  • MLのリポジトリとパイプラインのリポジトリは別
    • masterにマージされたら、CircleCIでビルド、更新

検索システムのMLRモデル更新の自動化

  • Solrのメンテナンス
  • 機械学習モデルのCI/CD
  • 元々はオブジェクトストレージに置いていたが、GitLFSでおけるようになった
  • モデルのバリデーション
  • 今後は、ベンチマーク、A/Bテスト、モデル選択も自動化したい
  • Cloud ComposerにてGKEPodOperatorで全て行うのであればArgoを使ってもよいのかなと感じたのですが

  • 実験でArgoベースのKubeflow Pipelineを使っていますね。 比較すると、Airflowはやや成熟してきており色々と高機能で安定感がある

  • 一方でkubeflow pipelineは我々が検討していた段階では色々と足りない機能(失敗したrunを途中から実行など)があり、鋭意開発中感があったので本番運用に使うのは怖かったです。

  • 素のargoは使ったことはないのですが、今だとGCPでkubeflowベースのAI Platform Pipelinesも出ています

全社共通レコメンドプラットフォームへのKubernetes/Airflow導入

  • オンプレk8s
  • ジョブ管理はAirflow
  • トラフィックは10k req/s
  • Pod operator
  • なぜAirflow
  • 特徴量管理
    • BQ?
  • 実験管理
    • Kedro + MLflow tracking
    • Jupyterの相性がいい
    • kedroはドキュメントが丁寧・使いやすさなどがいい点としてあげられますね。GUIの点では定常実行を考えるとAirflowがいいですが、実験の上では十分かなと。

スクラム開発でやっていること

はじめに

スクラム開発とは

アジャイル開発のやり方の一つ
「事前に全てを正確に予測し、計画することはできない」ということが前提となるプロジェクトにおいて、
・おおよその全体像はちゃんと明らかにしつつ、
・優先度(重要度が高い、リスクが高い)を常に整理しておいて、
・もちろん直近の作業は詳細化して取り組みつつ、
・こまめに期間を区切って継続的な関係者のフィードバックを得ながら計画を修正していくことで、
要求の分析や設計などに必要以上の時間をかけないように進めていく。

やっていること

1. 『プロダクトバックログ』のベース作成(by PO)@PJのはじめ

  • やること
    • 実現したい要求をリストにして並び替える
    • ストーリー/デモ手順(完了要件)/見積もり/スプリント
      • 見積もり/スプリントについては「スプリント計画ミーティング」にて記載する
  • ポイント
    • 優先度順をきっちり決めておく

2. 「スプリント計画ミーティング」(by PO/SM/Devチーム)@スプリントのはじめ

  • やること
    • 『プロダクトバックログ』を見ながら、次のスプリントで実施する項目を選択する
    • タスクに分解+見積もり+アサインする
    • -> スプリントバックログ(Issue)に
  • ポイント
    • 見積もり工数はフィボナッチ数で(不確実性を吸収)

3. デイリースクラム(by SM/Devチーム)@毎日

  • やることとポイント
# 目的
* スプリントのゴールを達成できるか検査する
* 残作業の追跡

# アジェンダ
* PJ概況
* 前回のデイリースクラムからやっていたこと
* 次回のデイリースクラムまでにやること
* 問題点

# 備考
* 15分厳守。問題点の話し合いは別途枠を設ける。
* Issueをメンテする。
* 「他の人に共有すべきこと」に重点を置く。

4. 開発(by Devチーム)@毎日

5. スプリントレビュー(by PO/Devチーム)@随時

  • やること
    • デモを実施し、完了要件を満たしているか確認する

6. スプリントレトロスペクティブ(by SM/Devチーム)@スプリントのおわり

  • やること
    • 振り返り
      • 予定と実績
      • 予定と実績の乖離を元に、KPT振り返り
  • ポイント
    • 次のスプリントをよりよく進めるための振り返り

補足

ロール

  • PO(プロダクトオーナー)
  • SM(スクラムオーナー)
    • 開発プロセスが上手く回るようにする
    • 妨害を排除し、支援と奉仕をする
  • Devチーム(開発チーム)
    • リリース判断可能なプロダクトを作る

GitHubActionsについて調べたことメモ

はじめに

  • GitHubActionsまわりについて復習したときのメモ

メモ

ファイル例

  • .github/workflows/action.yml
name: ci # ワークフロー名

on: [push] # リポジトリへのpush時にこのワークフローを実行するよう指定

jobs:
  build-image: # ジョブの名前(ワークフローの中の一つのジョブ)
    runs-on: ubuntu-latest # Ubuntuの最新版環境内で処理を実行することを指定
    steps: # ここで実行する処理やコマンドを指定する
    - uses: actions/checkout@main # リポジトリからのチェックアウトを行う「actions/checkout」アクションを実行する
    - name: Build Docker image
      run: |
        echo "start build"
        sudo apt-get install -y wget
        wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
        cp google-chrome-stable_current_amd64.deb docker/pytorch_1_4/
        cd docker/pytorch_1_4 && docker build --tag forecast-keiba:latest . && cd -
        cd docker/pytorch_1_4 && bash run_ci.sh docker && cd -
        echo "end build"

ファイルの補足

  • on: push

    • リポジトリへのプッシュ(「push」)
    • ブランチもしくはタグの作成および削除(「create」および「delete」)
    • プルリクエストやissuesの作成(「pull_request」および「issues」)
    • issuesへのコメント投稿(「issue_comment」)
    • リポジトリのフォーク(「fork」)
    • Wikiページの作成(「gollum」)
  • 特定のブランチやタグのみを対象に指定するとき

on:
  push:
    branches:
      - master
      - foo*
  • スケジュール実行
on:
  schedule:
    - cron: '30 * * * *'
  • runs-on

    • self-hosted: ユーザーが独自に用意した実行環境
  • steps以下ではrun要素ではなく「uses」要素を指定することで、任意のActionを実行したりできる

  • パラメータを変数で指定する

    • github.sha: コミットハッシュ
  • Secrets

- name: Exports SSH private key
      run: echo "${{ secrets.SSHPrivateKey }}" > id_rsa && chmod 600 id_rsa

その他

  • Action
    • upload-artifact: 指定したファイルを「artifact」として保存する
    • cache: 生成物をキャッシュして処理を高速化する
  • プラン
    • Free: 500MB/2000分
  • マシンスペック
    • Standard_DS2_v2(仮想CPU×2、メモリ7GB、ストレージ14GB)

参考

Ansibleについて調べたことメモ

はじめに

  • Ansibleまわりについて復習したときのメモ

メモ

ディレクトリ構成案

  • 『①inventoryで対象ホストと変数を設定し、②playbookでどのロール(各機能のタスク)を実行するかを定義して、③roleでplaybookから呼び出されるロール(各機能のタスク)を定義する』をシンプルに実現するディレクトリ構成とした。
  • 変数の格納場所が分散しないよう、host_varsに寄せる(varsディレクティブ、group_vars、defaults等は使わない)
  • →本来、group_varsに寄せるべき
├── inventory                       #
│   ├── prod                         # 環境ごとに分類する
│   │   ├── hosts                   #
│   │   └── host_vars               #
│   │       ├── <system>-web01.yml  # ホストごとに作成する(変数は基本的にここにのみ格納する)
│   ├── stg01                       #
│   ├── dev01                       #
│
├── playbooks                       #
│   ├── stable                      # stable環境とdevelopment環境で分類する
│   │   ├── <system>-web01.yml      # ホストごとに作成する
│   └── development                 #
│
└── role                            #
    ├── nginx                       #
    │   ├── tasks                   #
    │   ├── handlers                #
    │   ├── templates               # ファイルを配置する
    │   └── files                   # ファイルを配置する(tar,rpm等の一時配置ファイル)

ファイル例

  • inventory/prod/hosts
######
# NODES
######
[mgmt]
mgmt-01     ansible_host=10.1.1.10

[<system>-web-blue]
<system>-web01-blue ansible_host=10.0.0.11
<system>-web02-blue ansible_host=10.0.0.12

######
# SSH connection configuration
######
[all:vars]
# SSH User
ansible_user=ubuntu
ansible_ssh_private_key_file='~/.ssh/xxx-key.pem'

######
# Python configuration
######
[all:vars]
ansible_python_interpreter=/usr/bin/python3
  • inventory/prod/hosts_vars/-web01-blue
---
###################
# nginx
###################
nginx_deploy_module_nginx: nginx.zip
  • playbooks/stable/-web01-blue.yml(サンプル1)
---
- name: playbook of <system>-web01-blue.yml
  # 変数設定(playbook内で指定)
  vars:
    ansible_hostname: <system>-web01-blue
    ansible_role_dir: /opt/ansible/roles

  # 対象ホスト指定
  hosts: "{{ ansible_hostname}}"
  become: true

  # ロール指定
  roles:
    # common
    - role: "{{ ansible_role_dir }}/hostname"
    - role: "{{ ansible_role_dir }}/env"
    - role: "{{ ansible_role_dir }}/yum"
    - role: "{{ ansible_role_dir }}/user_app-user"
    - role: "{{ ansible_role_dir }}/user_ope-user"
    - role: "{{ ansible_role_dir }}/user_view-user"
    - role: "{{ ansible_role_dir }}/dir_work"
    - role: "{{ ansible_role_dir }}/dir_script"
    - role: "{{ ansible_role_dir }}/custom_metrics"
    - role: "{{ ansible_role_dir }}/awslogs_main"
    - role: "{{ ansible_role_dir }}/awslogs_deploy"
    - role: "{{ ansible_role_dir }}/backup_log"
    - role: "{{ ansible_role_dir }}/ds_agent"
    # individual
    - role: "{{ ansible_role_dir }}/disk_mount"
    - role: "{{ ansible_role_dir }}/nginx_main"
    - role: "{{ ansible_role_dir }}/nginx_deploy"
    - role: "{{ ansible_role_dir }}/java"
    - role: "{{ ansible_role_dir }}/tomcat_main"
    - role: "{{ ansible_role_dir }}/tomcat_deploy"
    - role: "{{ ansible_role_dir }}/oracle_client"
  • playbooks/mgmt.yml(サンプル2)
---
- name: Playbook of mgmt
  hosts: mgmt
  become: true
  vars:
    _role_dir: roles/
  roles:
    - role: "{{ _role_dir }}/hostname"
  • roles/nginx_main/tasks/mai.yml(サンプル1)
---
################################################################################
# nginx セットアップ
################################################################################
- name: create nginx_rpm dir
  file:
    path={{ nginx_main_dir_module }}
    state=directory
    owner={{ nginx_main_owner }}
    group={{ nginx_main_group }}
    mode={{ nginx_main_mode }}

- name: upload nginx_rpm
  copy:
    src="nginx-{{ nginx_main_rpm_ver }}.rpm"
    dest={{ nginx_main_dir_module }}

- name: yum nginx
  yum:
    name: "{{ nginx_main_dir_module }}/nginx-{{ nginx_main_rpm_ver }}.rpm"
    state: present

- name: start nginx and set auto start
  service:
    name: nginx
    state: started
    enabled: yes
  • roles/hostname/tasks/main.yml(サンプル2)
---
- name: Manage hostname
  hostname: name={{ inventory_hostname }}

運用ルール案

  • 変数には先頭にロール名をつける
  • モジュールの置き場
    • 大きい静的モジュール: confサーバの/var/moduleに置いておいて、filesにコピーする
    • ホストごとのモジュール(*_deployロール): confサーバの/var/deploy/に置いておいて、filesにコピーする