selmertsxの素振り日記

ひたすら日々の素振り内容を書き続けるだけの日記

Cloud FunctionsとCloud Schedulerを利用してDatadogで監視しているホスト数を通知させてみた

作ったもの

指定された期間のDatadogの監視台数について、最小,最大,合計(ホスト数×時間)を算出してSlackに通知してくれるCloudFunctionsを作成しました。そのCloudFunctionsはCloud Pub/Subで実行され、Cloud SchedulerはそのCloud Pub/SubのTopicを一日一度実行してくれます。

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f32333630362f32306237373066382d356364322d323232312d363762302d6139623430663830616433382e706e67_censored.jpg

コードはこちらです。 https://github.com/selmertsx/datadog_slack_report

この資料に記載されていること

  • Cloud SchedulerからCloud Pub/Subを実行する方法
  • DatadogでのRollup functionの使い方
  • ※ Cloud Functionsに関する詳細な説明はしません。

CloudFunctionsを実行するための方法については、こちらの記事を参照してください。

https://qiita.com/selmertsx/items/31b10bfc4b72b05627e1 https://qiita.com/selmertsx/items/27686e51b4471eaf8c86

作った理由

  • 今いる企業では複数のプロダクトでDatadogを利用してリソースの監視を行っていません
  • Datadogでの監視はアカウントを分けていません
    • サーバーメトリクスは企業の資産であり、すべてのエンジニアが隣のプロダクトのサーバーメトリクスや監視方法を見て学ぶことができるように、敢えてアカウントは分けないようにしています
    • ちょっと声を掛けて見せて貰えばいいでないという声はあるかと思いますが、気になったときに誰の手も煩わせずに確認できるようにしたいという意図でそうしています
  • アカウントを分けないで運用をしていると、プロダクト毎のコストを集計する必要がある
  • コストの集計は自動でやってしまいたい
  • 最近でた Cloud Schedulerを使ってみたい

事前準備

タグ付けを行ったら次の準備に進んでください。

DatadogでのQueryの実行

DatadogではAPIを実行して、メトリクスを取得することができます。さらに、それらメトリクスに対して様々な計算処理を入れることも可能です。その実行方法については、こちらの資料に記載されています。

さて、シンプルに各プロダクト毎のホスト数をAPIで取得したい場合、count:system.cpu.user{*} by {product}のようなクエリになります。ECインスタンスのメトリクスは5分毎にagentから送信されているため、1日分のデータを取得してしまうと12 * 24 * プロダクト数分のデータが必要になってしまいます。Datadogの課金体系から考えても、1時間未満の粒度でのデータは取得する必要がありません。そのため、不要なデータを削ぎ落とす必要があります。今回はRollup関数を使って、データを1時間単位で丸め込んでから取得しました。これによって24*プロダクト数分のデータにすることができました。Rollup関数の説明はこちらになります。抜粋した文書が下記になります。

Rollup The function takes two parameters, method and time: .rollup(method,time) The method can be sum/min/max/count/avg and time is in seconds. You can use either one individually, or both together like .rollup(sum,120).

この説明通りにクエリを作成してみると、count:system.cpu.user{*} by {product}.rollup(count, 3600) という形になりました。TypeScriptのコードは下記のようになります。

// https://github.com/selmertsx/datadog_slack_report/blob/master/src/DatadogClient.ts#L28
  public async countHosts(from: string, to: string): Promise<DatadogHostMetrics[]> {
    const params: CountHostRequest = {
      api_key: API_KEY,
      application_key: APP_KEY,
      from,
      query: `count:system.cpu.user{*} by {product}.rollup(count, 3600)`,
      to,
    };

    const res: DatadogQueryReponse = await this.request.get("/query", { params });
    return res.data.series.map((product: SeriesMetrics) => {
      const pointlists: PointList[] = product.pointlist.map((point: number[]) => {
        return { unixTime: point[0], count: point[1] };
      });
      return { product: product.scope, pointlists }
    });
  }

僕のコードの中にDatadogのAPIで帰ってくるレスポンスの型(の一部)を記載しておきましたので、データの中身について気になる方は目を通されると良いでしょう。

Cloud Functionsのデプロイ

Google Cloud Pub/SubをトリガーにCloud Functionsを起動する設定をしていきます。Google Consoleからdatadog_reportというtopicを作成し、下記のコマンドでCloud Functionsをデプロイしましょう。

gcloud beta functions deploy datadog_handler \
  --region=asia-northeast1 \
  --stage-bucket=datadog_report \
  --trigger-event=google.pubsub.topic.publish \
  --trigger-resource=datadog_report \
  --runtime=nodejs8

Cloud FunctionsのトリガーとしてCloud Pub/Subを利用する方法については、このドキュメントに詳細が記載されています。

Cloud Schedulerの設定

GCPのConsoleから、cloud schedulerを開いてみましょう。そこでcreate jobを選び、下記のように設定をします。ここでFrequencyが実行頻度を設定する部分です。cronと同じフォーマットで実行する頻度を設定することができます。そしてTargetがトリガーの設定部分です。ここではApp Engineのアプリケーション、Cloud Pub/Sub, HTTP Requestの3つを設定することができます。今回のCloudFunctionsはPub/Subをトリガーにしているので、ここではPub/Subを指定しておきましょう。

スクリーンショット 2018-11-11 22.53.10.png

create ボタンを押せば終了です。気になるお値段ですが、こちらのドキュメントによると1人あたり3ジョブまで無料ということです。

動作確認

スクリーンショット 2018-11-11 22.59.31.png

すぐに動作確認をしたいので、上記画面のRun now ボタンをクリックし、Pub/SubのTopicに対してメッセージを送ってみましょう。するとSlackに下記のようにメッセージが送信されます。

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f32333630362f32306237373066382d356364322d323232312d363762302d6139623430663830616433382e706e67_censored.jpg

以上で、Cloud FunctionsとCloud Schedulerを利用してDatadogで監視しているホスト数を通知させることができました。今後は、集計期間をpub/subのメッセージで設定できるようにするなど機能を追加していく予定です。もし、使ってくださる方がいて、追加で欲しい機能などございましたら、お気軽にIssue登録おねがいします〜

Azure ADとG Suiteのグループを同期させる

モチベーション

  • メーリスの設定を自動でして欲しい
  • Google Drive内の資料の閲覧権限を自動かつ適切に付与したい
  • 誰がどのようなグループに所属し、どのような権限を持つかはAzure ADで制御したい

やり方の方針

  • Azure ADとG SuiteのGroupを同期させる
  • Azure ADをマスターとし、G Suite内でグループの設定変更はしない
  • Azure ADの動的グループメンバーシップという便利機能を使って、グループに所属するメンバーを自動で追加・削除する

本資料で記載すること

  • AzureAD と G Suiteのグループを同期させるところまで
  • 動的グループメンバーシップについては次回

前提条件

  • Azure ADのEnterprise Application設定権限を持っている
  • G Suiteの特権管理者権限を持っている

やり方

https://docs.microsoft.com/ja-jp/azure/active-directory/saas-apps/google-apps-provisioning-tutorial#enable-automated-user-provisioning

基本的にこちらのドキュメントどおりに設定します。ここでは、上記ドキュメントの補足のみを行います。

Configure automatic user account provisioning

基本的にドキュメントの通り設定すれば良いです。

手順7: Admin API Privileges

手順に記載されている画像を見ると全てのチェックボックスがチェックされているように見えますが、僕が見た画面は何もチェックされていませんでした。

f:id:selmertsx:20181017153836p:plain

不安はあったものの、User画面から自分の権限を確認したら、ちゃんと全ての権限があったのでとりあえず先に進みました。それで問題なかったです。

f:id:selmertsx:20181017153855p:plain

手順10: Provisioningの設定

もし、G Suiteと同期するAzure ADのグループを絞る必要があるのであれば、 Provisioning の設定をする前に同期対象のグループを指定する必要があります。Users and groups タブからこんな形でユーザー・グループを設定しました。

f:id:selmertsx:20181017153817p:plain

手順13: Azure ADにG Suiteの権限委譲

Azure ADからの許可を求められる画面イメージが、サンプル画像と違ったので一応載せておきます。

f:id:selmertsx:20181017153532p:plain

手順17: Mappings

今回はGroupの同期がしたかったのでMappingsの設定を Synchronize Azure Active Directory Groups to GoogleApps にしました。また、SettingsのScopeについては、任意のグループのみを同期したいのであれば Sync only assigned users and groups に設定する必要があります。

f:id:selmertsx:20181017153638p:plain

結果

  • グループが問題なく作られている
  • グループ内にメンバーも所属している

f:id:selmertsx:20181017153758p:plain

疑問点

  • グループのオーナーを指定する場所が無かった
  • グループのメールアドレスをどのように指定するのか

G Suite Domain-Wide Delegationを使ってG SuiteのGroupを作成する

TL;DR

  • googleapisを使って、G Suiteの Groupを作成できるようにした
  • 公式のドキュメントにはnodejsでの実装方法が無かったので自分で実装した
    • よってこれが最適な方法かは分からない
  • G Suiteの特権管理者の権限が付与されている必要がある

https://developers.google.com/admin-sdk/directory/v1/guides/delegation 大体このドキュメントに沿って作った

手順

  • GCP上にサービスアカウントを作成する
  • G SuiteのAdmin consoleにて、GCPのサービスアカウントに G Suite APIを叩くための権限を付与する
  • Groupを作成するコードを実装する

GCP上にサービスアカウントを作成する

  • GCP consoleにてservice accountのページを開き、新しく service accountを作成する
  • Enable Google Apps Domain-wide Delegation にチェックを入れる
  • 鍵を作ってDLする
  • 鍵のjsonを開き、client_idをメモしておく

スクリーンショット 2018-10-16 16.41.37.png

GCPのサービスアカウントにG Suiteの権限を付与する

  • G Suiteのadmin consoleを開く
  • Advanced settingsを選ぶ
  • AuthenticationセクションのManage API client accessを選ぶ
  • Client Nameの部分にサービスアカウント作成時にメモしておいた client idを設定する
  • scopesの部分にhttps://www.googleapis.com/auth/admin.directory.group を設定する
  • authorize を押す

スクリーンショット 2018-10-16 20.44.57.png

Groupを作成するコード

import { google } from "googleapis";
import path from "path";

async function main() {
  const jwtClient = new google.auth.JWT({
    keyFile: path.join(__dirname, "credentials.json"),
    scopes: ['https://www.googleapis.com/auth/admin.directory.group.readonly'],
    subject: process.env.SUBJECT
  });

  await jwtClient.authorize()

  const admin = google.admin({
    version: 'directory_v1',
    auth: jwtClient
  });

  const response = await admin.groups.list({ domain: process.env.DOMAIN });
  console.log(response.data);
}

main();

結果

$ npx ts-node create_group.ts
{ kind: 'admin#directory#group',
  id: 'xxx',
  etag: '"xxx"',
  email: 'sample-group@xxx.jp',
  name: 'sample-group',
  description: 'hogehoge',
  adminCreated: true }

こんな感じでグループができました

TypeScriptで書いたCloud FunctionsをCloudBuildを使ってDeployする

これを読んで出来るようになること

TypeScriptで書いたCloud Functionを、Code Buildを使ってdeployできるようになる。

前提と事前準備

前提

  • CloudFunctionsのコードはwebpackを使ってまとめている
  • Deployは serverless コマンドではなく、gcloudコマンドで行っている

事前準備

過去の記事

selmertsx.hatenablog.com

設定

ディレクトリ構造

cloud_source_repository_test git:(master) tree -I node_modules
├── README.md
├── cloudbuild.yaml
├── package-lock.json
├── package.json
├── src
│   └── index.ts
├── tsconfig.json
└── webpack.config.js

TypeScriptのサンプルコード

import { Decimal } from "decimal.js";

function subscribe(req: any, res: any){
  const x = new Decimal('0xff.f')
  return res.send(`Hello World! ${x}`);
}

export { subscribe }

cloudbuild.yamlの設定

steps:
  - name: node:8
    entrypoint: npm
    args:
    - install

  - name: node:8
    entrypoint: npm
    args:
    - run
    - build

  - name: 'gcr.io/cloud-builders/gcloud'
    args:
    - functions
    - deploy
    - subscribe
    - --stage-bucket=xxx
    - --trigger-http

buildコマンドの中では webpack --mode productionが実行されています。

tsconfig.jsonの設定

{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "strict": true,
    "preserveConstEnums": true,
    "strictNullChecks": true,
    "sourceMap": true,
    "outDir": "./",
    "moduleResolution": "node",
    "esModuleInterop": true
  },
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

Webpackの設定

module.exports = {
  entry: "./src/index.ts",
  target: 'node',
  output: {
    path: __dirname,
    filename: 'index.js',
    libraryTarget: 'this'
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader'
      }
    ]
  },
  resolve: {
    extensions: [ '.js', '.json' ]
  }
}

IAM Roleの設定

f:id:selmertsx:20181009192122p:plain

動作確認

$ curl https://xxx.cloudfunctions.net/subscribe
Hello World! 255.9375%

備考

webpackを利用する理由

  • node_modulesも、Cloud Functionにuploadすればwebpackを利用する必要は無い
  • CodeBuild内でBuildとdeployをするため、Development環境用のpackageもinstallは必須
  • Development環境用のpackageもgcloud コマンドでdeployするとファイルサイズが大きくなる

残タスク

GCPのCloud BuildでCloudFunctionsをデプロイする

モチベーション

GitHub上で管理しているCloud Functionsのコードについて、masterにマージされたタイミングでdeployして欲しい。

Cloud Buildとは何か?

Docker Image作ったり Cloud Functionsをdeployしたり、テストしたりできます。CircleCIみたいなものをイメージして頂けるとよいかなと思います。各タスクに必要なDocker Imageについて、GCPが提供している公式のイメージが使え、GCP内のリソースにもアクセスできるので、GCP固有の機能を使う際にはCloud Buildを用いるのが便利なのではないでしょうか。GCPを利用しているサービスでCloud Buildを利用すると、Credentialsな情報をGCP内のみで扱うことができるというメリットもあります。

今回やることの一覧

  • Cloud Source Repositoriesを作成
  • Cloud Source RepositoriesとGitHubを連携
  • Cloud Buildの Triggerを設定
  • Cloud Buildの build config fileを設定

事前準備

今回デプロイしたいコードを用意したGitHubリポジトリを作成しておきます。今回はサンプルとして下記のリポジトリを用意しました。

https://github.com/selmertsx/cloud_source_repository_test

Cloud Source Repositoriesの設定

Cloud Source Repositoriesに関して設定しなければならない項目は下記2点です。全部で10分程度で終わります。

  • Cloud Source Repositoriesを作成
  • Cloud Source RepositoriesとGitHubを連携

この設定ですが、基本的に公式で提供されているこの手順通りにやれば大丈夫です。すると、このように連携可能なGitHubリポジトリの一覧が出てくるので、ここから目的のものを選びましょう。今回はcloud_source_repository_testを選択します。

f:id:selmertsx:20181005170848p:plain

Cloud Build Triggerの設定

  • Cloud Buildのトリガー設定ページにアクセス
  • Add Triggerを選択
  • Cloud Source Repositories のリポジトリを選択
  • 先程設定したCloud Source Repositoriesを選択
  • Triggerの設定をする
    • 今回はmaster branchの変更時にcloud buildを実行したいので、下記のように設定した

f:id:selmertsx:20181005170953p:plain

cloud functionsの実装

最初にコードをuploadする際のbucketを用意します。

$ gsutil mb gs://selmertsx-sample-bucket
Creating gs://selmertsx-sample-bucket/...
exports.subscribe = (event, callback) => {
  return callback(null, "Success");
}

今回はシンプルにこれだけです!

build config fileの設定

build config fileの設定については、下記資料を参考にして行いました。

steps:
  - name: 'gcr.io/cloud-builders/gcloud'
    args:
    - beta
    - functions
    - deploy
    - subscribe
    - --stage-bucket=selmertsx-sample-bucket
    - --trigger-topic=cloud-builds

上記ファイルをloud_source_repository_testのルートディレクトリに置いて、master branchにpushします。すると下記のようにcloud buildが実行されます。

f:id:selmertsx:20181005170801p:plain

今回のjavascriptコードはcloud buildの実行をtriggerにしているので、deployされたコードはそのまま実行されちゃいます。その結果がこちらです。

f:id:selmertsx:20181005170712p:plain

というわけで、Cloud BuildからCloud Functionsがデプロイできるところまで確認できました。

「Amazon Web Services サーバレスレシピ」という本をリリースしました

FiNCのエンジニアの方々と一緒に書いた本が、この度 インプレス R&D様から出版していただくことになりました。

僕は最初の章を書かせて貰っています。宜しければ見てやってください〜。

CloudFunctionsをServerless Framework & TypeScriptの環境で試してみる

やったこと

Serverless Frameworkを使って CloudFunctionを動かしてみました。 試した内容は下記の通りです。

  • TypeScriptで書けるようにしてみる
  • serverless.ymlの設定で環境変数を利用する
  • 日本のリージョンにDeployする

実際に利用したコード https://github.com/selmertsx/study/tree/master/serverless/cloudfunction-sls-sample

前提

https://serverless.com/framework/docs/providers/google/guide/quick-start/

上記チュートリアルを実行して、nodejsのコードをdeploy & invokeできるようにしてある前提で話を進めます。

TypeScriptで書けるようにする

https://github.com/prisma/serverless-plugin-typescript このpluginを利用します。

手順は簡単で、下記の手順を踏むだけで設定可能です

  • serverless.yml のpluginsに serverless-plugin-typescript を追加する
  • 指定されたフォーマットでtsconfig.jsonの設定をする
  • 既存のコードをTypeScriptに置き換える
  • (options) package.jsonのmain fieldを書き換える
plugins:
  - serverless-google-cloudfunctions
  - serverless-plugin-typescript
// Note: outDir と rootDir optionsを上書きしないこと
{
  "compilerOptions": {
    "target": "es5",
    "outDir": ".build", 
    "moduleResolution": "node",
    "lib": ["es2015"],
    "rootDir": "./"
  }
}

もし起点となるコードが index.ts でなければ、package.jsonのmain fieldを下記のように書き換える必要があります。

// handler.tsを起点にしたい場合
{
  // ...
  "main": "handler.js",
  // ..
}

serverless.ymlの設定で環境変数を利用する

https://serverless.com/framework/docs/providers/google/guide/variables/

公式ドキュメントを見ると、ymlやjsとして別のファイルに切り出したり、serverless.dev.ymlのように環境毎にymlを分けたりする方法が書かれています。今回はgitにcommitしたくないコードの管理がしたかったので、ちょっとその用途とは違うため別の方法を模索しました。

https://serverless.com/framework/docs/providers/aws/guide/variables/

awsの方のGuideを見てみると、下記のような機能がありました。

o reference environment variables, use the ${env:SOME_VAR} syntax in your serverless.yml configuration file. It is valid to use the empty string in place of SOME_VAR. This looks like "${env:}" and the result of declaring this in your serverless.yml is to embed the complete process.env object (i.e. all the variables defined in your environment).

これについてgoogleでも使えないかと試してみたところ、普通に使えたので今回はそれを利用しました。

service: cloudfunction-sls-sample
provider:
  name: google
  project: ${env:PROJECT}

日本のリージョンにDeployする

cloud functionsでServerless Frameworkを利用し、regionを何も指定せずにdeployをすると us-central1にdeployがされます。 deploy先のregionをserverless.ymlで設定していきましょう。 deployする先の指定は、serverless-google-cloudfunctionsのv2.0.0 から出来るようになっています。 けれども、sls コマンドでinstallされるversionはv1.2.0なので、pluginのバージョンを上げる必要があります。

https://github.com/serverless/serverless/blob/63b8dafbeb7bd463acf9fe9a4f5ec51c7e972928/lib/plugins/create/templates/google-nodejs/package.json#L12

"devDependencies": {
  "serverless-google-cloudfunctions": "^2.0.0",
  "serverless-plugin-typescript": "^1.1.5"
}

その後、serverless.ymlの値を書き換えて実行してみます。

service: cloudfunction-sls-sample
provider:
  name: google
  region: asia-northeast1
  runtime: nodejs8
  project: ${env:PROJECT}

実行結果がこちらになります。

$ sls deploy
service: cloudfunction-sls-sample
project: xxx
stage: dev
region: us-central1

Deployed functions
first
  https://us-central1-xxx.cloudfunctions.net/http

ぱっと見るとus-central1にdeployされていてギョっとしますがcliを使って実際にdeployされているregionを確認してみると、ちゃんとasia-northeast1になっていることが確認できます。

gcloud beta functions describe http --region asia-northeast1
availableMemoryMb: 256
entryPoint: http
httpsTrigger:
  url: https://asia-northeast1-xxx.cloudfunctions.net/http <==ここをチェック
labels:
  goog-dm: sls-cloudfunction-sls-sample-dev
name: projects/xxx/locations/asia-northeast1/functions/http
runtime: nodejs8
serviceAccountEmail: xxx@appspot.gserviceaccount.com
sourceArchiveUrl: gs://sls-cloudfunction-sls-sample-dev-xxx/serverless/cloudfunction-sls-sample/dev/xxx-2018-09-25T10:08:07.390Z/cloudfunction-sls-sample.zip
status: ACTIVE
timeout: 60s
updateTime: '2018

AWS Lambdaと比較した所感

  • localでの動作確認について、lambdaはpluginで提供されているが、cloud functionsはまだない。
  • LambdaではTypeScriptテンプレートがあるので、特に何の指定もしなくて良い
  • Lambdaは起動するためのeventsが豊富
    • cloud functionsはhttpとpub subだけ
    • これがcloud functionsのデメリットとなるかは、pub subを触ってみないとなんとも言えない

コミットチャンス

宿題

  • credentialsや環境変数をcode buildなどから扱えるようにしたい
  • cloud functionに渡す権限を、もっとちゃんと整理したい