寝ても覚めてもこんぴうた

プログラム書いたり、ネットワーク設計したり、サーバ構築したり、車いじったり、ゲームしたり。そんなひとにわたしはなりたい。

minikubeでspinnakerを動かすまで(Macのローカルでspinnakerを動かす)

とりあえずサクッと動かせる環境が欲しかったので、ローカルのminikubeでも一応spinnakerを動かすことはできそうだったので試す。

事前準備

  • helmがインストールが完了していること (brew install kubernetes-helm)
  • kubectlのインストールが完了していること
  • minikubeのインストールが済んでいること

注 : Spinnakerをminikubeで動かす場合にはかなりタフなマシンスペックが必要になります。メモリが16GB程度は搭載されているMacでもだいぶ厳しいです。

minikubeを起動する

minikubeを新たなまっさらな状態で起動します。
Spinnakerがリソースを大量に必要とするためです。

minikubeを起動する前にminikubeのupdateをしておくことをおすすめします。
brew caskでインストールしている場合には brew cask reinstall minikube でupdateされると思います。
すでにminikubeの環境を作成したことがある場合には、 minikube delete を実行し、minikubeのVMを削除しておくと良いと思います。

以下のコマンドでminikubeを起動します。

minikube start

minikubeのVMのパラメータを変更する

minikubeのデフォルトだとメモリもCPUも足りないため、spinnakerが起動することすらできないので、VirtualBoxのパラメータを変更する必要がある。

minikubeを止める

以下のコマンドで起動したばかりのVMを停止しましょう

minikube stop

VirtualBoxの設定からcpu/memoryの設定を更新する

メモリは8192MB、CPUは4コアを設定した。

f:id:chidakiyo:20181001191348p:plain

f:id:chidakiyo:20181001191431p:plain

minikubeを改めて起動する

minikube start

kubectlが期待するクラスタ(minikube)に向いているかチェック

minikube startを実行した時点で設定が変更されているはずだが一応確認する

kubectl get node

以下のような感じでminikubeと出れば大丈夫

NAME       STATUS    ROLES     AGE       VERSION
minikube   Ready     master    5m        v1.10.0

helmをクラスタに対して設定する

以下のコマンドでクラスタに対してhelmを有効化する

helm init

Spinnakerをインストールする

helmコマンドを利用し、Spinnakerをインストールする

helm install stable/spinnaker --namespace spinnaker --timeout 6000

少しタイムアウトを伸ばしたほうが良いです。

インストールが完了すると以下のような表示が出ます。

NOTES:
1. You will need to create 2 port forwarding tunnels in order to access the Spinnaker UI:
  export DECK_POD=$(kubectl get pods --namespace spinnaker -l "cluster=spin-deck" -o jsonpath="{.items[0].metadata.name}")
  kubectl port-forward --namespace spinnaker $DECK_POD 9000

2. Visit the Spinnaker UI by opening your browser to: http://127.0.0.1:9000

To customize your Spinnaker installation. Create a shell in your Halyard pod:

  kubectl exec --namespace spinnaker -it jazzy-shrimp-spinnaker-halyard-0 bash

For more info on using Halyard to customize your installation, visit:
  https://www.spinnaker.io/reference/halyard/

For more info on the Kubernetes integration for Spinnaker, visit:
  https://www.spinnaker.io/reference/providers/kubernetes-v2/

表示がされてもすぐにPodが完全に起動しておらず、ポートフォワードの設定が失敗するので、以下のコマンドでPodのデプロイ状況を確認します。

watch -d -n 2 kubectl get pods --namespace spinnaker

STATUS がすべてRunningになるまでじっと待ちましょう・・・

Spinnakerに接続するためのポートフォワード設定をする

export DECK_POD=$(kubectl get pods --namespace spinnaker -l "cluster=spin-deck" -o jsonpath="{.items[0].metadata.name}")
kubectl port-forward --namespace spinnaker $DECK_POD 9000

ブラウザでアクセスする

ポートフォワードの設定がエラーなく実施できたらブラウザからいかのURLへアクセスします。

http://localhost:9000

f:id:chidakiyo:20181001191529p:plain

このような画面が表示され

f:id:chidakiyo:20181001191555p:plain

こちらのコンソールに遷移すればSpinnakerの準備が整いました!
あとは設定をしていろいろ使ってみることができます。ですが結構重いです。

ではでは。

Raspberry Pi : Mackerelで監視してみる

エージェントのダウンロード

ダウンロードするディレクトリに移動し、

cd ~

arm用のバイナリをダウンロードする。
こちら から実行時点で最新のものを利用するのが良いと思います。

wget https://github.com/mackerelio/mackerel-agent/releases/download/v0.56.1/mackerel-agent_linux_arm.tar.gz

tarの展開

tar zxvf mackerel-agent_linux_arm.tar.gz

Mackerelエージェントのインストール

展開したディレクトリに移動し、

cd mackerel-agent_linux_arm

agentを配置する

sudo cp -p mackerel-agent /usr/local/bin/

設定ファイルを配置する

sudo mkdir /etc/mackerel-agent

sudo mv mackerel-agent.conf /etc/mackerel-agent/

APIキーの設定

sudo mackerel-agent init -apikey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

自動で起動するようにsystemdに登録する

設定ファイルを作成する

/etc/systemd/system/mackerel-agent.service

内容は以下のように

[Unit]
Description=mackerel-agent

[Service]
Type=simple
WorkingDirectory=/var/tmp
ExecStart=/usr/local/bin/mackerel-agent --conf=/etc/mackerel-agent/mackerel-agent.conf
TimeoutStopSec=5
StandardOutput=null
User=root
Group=root

[Install]
WantedBy = multi-user.target

自動起動を有効化

登録し、

sudo systemctl enable mackerel-agent

自動起動させる

sudo systemctl start mackerel-agent

動作確認

sudo systemctl status mackerel-agent

というコマンドを実行しエラーが出ていないようであればひとまずok.

あとは、MackerelのWEB UIの方に情報が表示されていればばっちり。

helm install しようとしたら Error: could not find tiller のようなエラーが発生したので解決した

タイトルの通りなんですが、helmでローカルのminikubeにhelmインストールをしようと思い、helm install ~~ のような感じでコマンドを実行したがエラーが発生してうまくいかない。
おそらく、helmはインストールしたあとに、GKEのクラスタに対して初期設定を行っていたのでminikubeに対して実行する状態になっていないっぽい。

なので対応していきましょう。

helmがminikubeを向くようにする

helm自体はkubectlで設定したクラスタに向くようになっているようだ。
なので、kubectlコマンドで変更する

kubectl config use-context minikube

みたいな感じ。

でもこれで解決ではなく、tillerがいないと言われているのはminikube側にすでに向いているがminikubeクラスタにはtiller(helmのサーバ側で動くエージェント)が存在していないということ。

なので、tillerをインストールする。

tillerのインストール

tillerの簡単なインストール方法は helm init を実行する。 なので、以下のコマンドを実行する

helm init

tillerの動作チェック

tillerが動いているかは、kube-systemネームスペースに tiller が動いていればOKです。

なので以下のようなコマンドでチェックする

kubectl get all --namespace kube-system

上記コマンドを実行して tiller-* の・ようなものが動いているようであれば(statusがrunning)大丈夫そう

まとめ

ここまでやれば、helm install ~~ が無事に動くはず。

参考

helm ドキュメント

Appengine (Node.js/Standard) 環境でHeadless Chromeを利用してサイトのスクリーンショットを取得する

過去に2回ほどGAE/Nodeの記事 これこれ を書きましたが、もともとはこのHeadless ChromeをAppengine上で利用したかったのでそれを試したかったため。

Headless Chromeを利用することで、Webページのスクリーンショットだったり、JavaScriptが動いていないとデータの取得が難しいSPA的なページのクローリングを行ったりすることができる。

基本的には こちら のドキュメントをなぞって進めます。

というわけで早速スクリーンショット取得を試してみましょう!

node projectの初期設定を行う

nodeのプロジェクトを作成するディレクトリへ移動し、以下のコマンドを実行します。

npm init

基本的にはすべて エンター で進めて良いと思います。

npm startのスクリプトを追加する

以下の値を package.json 内に追加します

"scripts": {
  "start": "node app.js"
}

expressとpuppetterを依存モジュールとして追加する

以下のコマンドを実行し、インストールされるのを待ちます。

npm install express puppeteer --save

app.jsファイルを作成し処理を記述する

app.js ファイルを作成し、以下の内容を記述します

const express = require('express');
const puppeteer = require('puppeteer');
const app = express();

app.use(async (req, res) => {
  const url = req.query.url;

  if (!url) {
    return res.send('Please provide URL as GET parameter, for example: <a href="/?url=https://example.com">?url=https://example.com</a>');
  }

  const browser = await puppeteer.launch({
    args: ['--no-sandbox']
  });
  const page = await browser.newPage();
  await page.goto(url);
  const imageBuffer = await page.screenshot();
  browser.close();

  res.set('Content-Type', 'image/png');
  res.send(imageBuffer);
});

const server = app.listen(process.env.PORT || 8080, err => {
  if (err) return console.error(err);
  const port = server.address().port;
  console.info(`App listening on port ${port}`);
});

ローカルでテスト実行する

以下のコマンドを実行し、ローカルでテスト用サーバを起動します。

npm start

起動したコンテンツには http://localhost:8080/?url=https://example.com という形のURLでアクセスします。

キャプチャした画像が画面に表示されれば成功です。

作成したアプリケーションをデプロイする

デプロイする際には app.yaml が必要です
Headless Chromeを利用する場合には多くのメモリを必要とするため、以下のように instance_class を指定することが推奨されているようです。
ただし、インスタンスクラスを上げることで無料枠が減るので注意しましょう。

runtime: nodejs8
instance_class: F4_1G

Appengineにデプロイします

app.yaml が配置されたディレクトリで以下のコマンドを実行します。

gcloud app deploy --project {PROJECT_ID}

デプロイしたサービスを確認する

以下のコマンドを実行するとブラウザが起動します

gcloud app browse --project {PROJECT_ID}

http://YOUR_PROJECT_ID.appspot.com/?url=https://example.com のようにアクセスすると、example.comにアクセスしたスクリーンショットが表示されます。

これでいろいろ楽しいことができそう!

enjoy!

Building a Node.js App on App Engine なぞってみる

この記事は Building a Node.js App on App Engine をなぞってみた話です。

前回の記事 をふまえて、Node.jsのappを作成してGAEにdeployしようという記事です。

1. Creating a Google Cloud Platform project

GCPにプロジェクトを作りましょう。
あと、appengineを有効化してregionを設定します。
(雑)

まぁ、ここはそんなに難しくないということでこんな感じで。

2. Writing a web service with Node.js

Node.jsのコードを書きます。
ドキュメントには Node.js 8(LTS) をインストールするように書いてありますが、前回の記事にも書いた理由で node v10.9.0 で進めてみます。

キーポイント

  • 依存関係はpackage.jsonに記述します(Nodeやっている人なら説明いらないですね)
  • npm start でアプリケーションを起動できますよ。(ローカル実行ですね)
  • サーバは process.env.PORT で指定されたポートをlistenしておきましょう
  • Appengineにデプロイする際には app.yaml が必要です(GAE使い慣れている人なら説明いらないですね)

サーバを実装します

サンプルでは Express を利用していますが、何を利用してもいいようです。
特にこだわりはないので Express を利用してみます。

ドキュメントをそのまま実行せず以下の手順で行ってみます。

nodeプロジェクトの初期設定をします。

npm init

よくわからなければだいたい全部 エンター で大丈夫ですw

Expressを依存モジュールとして取得する

npm install express --save

index.jsを実装する

以下のような内容で server.js ファイルを作成します。

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('オレオレ!!オレだよ!');
});

// Listen to the App Engine-specified port, or 8080 otherwise
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}...`);
});

npm start設定をpackage.jsonファイルに投入する

package.json ファイルを開いて以下の内容をJSONとして正しい形で追記します。

"scripts": {
  "start": "node server.js"
}

全体的には以下のようなかたちになります

{
  {
    // ~~ 省略 ~~
  },
  "scripts": {
    "start": "node server.js"
  }
}

サーバをテスト実行してみます

作成したコードをローカルでテスト実行してみます。

npm start

を実行すると 8080 ポートでサーバが起動した旨のメッセージが表示されます。
ブラウザから http://localhost:8080 へアクセスしてみましょう

app.yamlファイルを作成する

Appengineにデプロイするためには app.yaml ファイルが必要です。

最低限、以下の設定が必要です。

runtime: nodejs8

ファイル構造は以下のようになります

my-nodejs-service/
  app.yaml
  package.json
  server.js

3. Deploying your service on App Engine

キーポイント

gcloud app deploygcloud app browse を利用します

Appengineにデプロイします

app.yaml が配置されたディレクトリで以下のコマンドを実行します。

gcloud app deploy --project {PROJECT_ID}

デプロイ時間を計測するため、timeコマンドを利用して time gcloud app deploy --project {PROJECT_ID} のようなかたちで実行したところ、1分10秒ぐらいでした。まぁまぁ速い。

デプロイしたサービスを確認する

以下のコマンドを実行するとブラウザが起動します

gcloud app browse --project {PROJECT_ID}

ブラウザで期待する文字が表示されればデプロイ成功です!!

4. Updating your service

入力フォームを作ります

先程のディレクトリ配下に以下のような内容で views/form.html ファイルを作成します

<!DOCTYPE html>
<html>
  <head>
    <title>My App Engine App</title>
  </head>
  <body>
    <h2>Create a new post</h2>
    <form method="POST" action="/submit">
      <div>
        <input type="text" name="name" placeholder="Name">
      </div>
      <div>
        <textarea name="message" placeholder="Message"></textarea>
      </div>
      <div>
        <button type="submit">Submit</button>
      </div>
    </form>
  </body>
</html>

配置するディレクトリは以下のようになります

my-nodejs-service/
  views/
    form.html
  app.yaml
  package.json
  server.js

フォームの表示

server.js にpathモジュールのimportを追加します。

const path = require('path');

submitのハンドラを追加します

app.get('/submit', (req, res) => {
  res.sendFile(path.join(__dirname, '/views/form.html'));
});

サブミットされたデータをハンドリングする

body-parserを依存に追加する

npm install body-parser --save

server.js に以下を追加する

const bodyParser = require('body-parser');

expressがbody-parserを利用するように server.js に設定する

app.use(bodyParser.urlencoded({ extended: true }));

データを読み込むためのPOSTハンドラを server.js に追加する

app.post('/submit', (req, res) => {
  console.log({
    name: req.body.name,
    message: req.body.message
  });
  res.send('Thanks for your message!');
});

ローカルでチェックする

npm start

コマンドでローカルサーバを起動し、 http://localhost:8080/submit へアクセスして確認を行う。
項目を入力し、submitすると、 Thanks for your message! とブラウザに表示され、コンソール側にはサーバ側で受け取った値が表示されれば成功。

変更をデプロイする

以下のコマンドで変更したコード一式をデプロイする

gcloud app deploy --project {PROJECT_ID}

デプロイしたサービスを確認する

確認も以下のコマンドから行える。

gcloud app browse --project {PROJECT_ID}

ローカルへのデプロイと異なり、Appengine側にデプロイした場合のログは、Stackdriver Loggingに送られる

5. Viewing Your Service's Logs

ログへのアクセスは GCP コンソールの Stackdriver Logging から行える。

f:id:chidakiyo:20180906190732p:plain

細かい使用方法は書きませんが、それほど使い方も難しくなく、複数スケールした場合でもこの1画面ですべて確認できるので非常に便利です!

enjoy!

Appengine Node.js Standard Environmentをデプロイしてみる(quickstart)

まず初回はquickstartに倣ってデプロイしてみるだけ。
ほぼ こちら のクイックスタートのままやってみようと思う。

事前に必要なもの(というかもう設定済みなので)

  • GCPのアカウント作成(gmailアドレスあればすぐ)
  • GCPのプロジェクト作成(適当にGCPコンソールからポチポチっと)
  • Google Cloud SDK(gcloudコマンド)のインストール
  • gcloudコマンドのログイン処理
  • nodeのインストール(npmコマンド使うらしいので)

ドキュメントには、GCPコンソールからプロジェクトを作成して、アプリケーションリソースのリージョンを指定して、課金を有効してーって書いてありますね。ま、その辺やっておきましょう。

Githubにあるサンプルコードをダウンロードする

適当なディレクトリでgit cloneをします。

git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

ですね、当たり前のやつです。

nodejs-docs-samples というディレクトリが落ちてきますが、その中の appengine/hello-world/standard というのが今回使うディレクトリです。
他にも山のようにディレクトリありますが、参考になりそうなので時間のあるときにでも目を通しておきましょう。

対象のディレクトリに移動

cd nodejs-docs-samples/appengine/hello-world/standard

を実行

依存関係のインストール

依存モジュールのインストールを行います。
最新のnodeでやろうと思いましたが、nodebrewの設定がおかしくなっていたようなので、 node v10.9.0 で実行します。

npm install

ローカルhttpサーバで実行してみます。

npm start

ローカルホストの 8080 ポートで起動するようなので

http://locahost:8080

へブラウザでアクセスしてみます。

Hello, world! と表示されれば成功しています。

先程サーバを起動したターミナルを抜ける場合には ctrl + c でquitできます。(画面に出てますね。一応親切心で書きましたw)

デプロイしてみる

事前にGCPのプロジェクトの作成は済んでますか?
appengineのregionの設定は済んでますか?

では、okdであればデプロイしましょう。

gloud app deploy --project {PROJECT_ID}

quickstartのドキュメントには gcloud app deploy だけで良いとありますが、gcloudコマンドに事前にcurrentのprojectを設定していない場合などハマる場合があるので引数つけてます。

ちなみに time app deploy --project {PROJECT_ID} のような形で初回デプロイを計測しましたが、 2分20秒ほどかかりました。
2回目からはもう少し速いはずです。

デプロイしたアプリケーションにアクセスしてみる

デプロイしたログに

Deployed service [default] to [https://{PROJECT_ID}.appspot.com]

と表示されているのでそのURLに直接ブラウザからアクセスしてもOKです。

コマンドでカッコよくアクセスしたい場合には

gcloud app browse --project {PROJECT_ID}

のようにコマンドを実行すれば自動的にブラウザが起動するはずです。

GAE/SE nodeのデプロイ簡単ですね!

Appengineで初回1回だけ実行したい(設定の初期化)ときはwarmupを利用する

Appengine (GAE) はリクエストのイベントでインスタンスが起動されるので、通常のアプリケーションを実装した際に初回1回だけ実行する処理というのが普通に実装するとなかなか難しい。
特にDB(Datastore)アクセスなどが必要な場合にはrequestが無いとappengineのcontextを作成することができないのでそもそもDB(Datastore)へのアクセスができない。

そんなときに利用するのがwarmupリクエスト。

warmupリクエストとは

最初に書いたとおりでappengineはリクエスト駆動で動くため、warmupのリクエストがappengineの環境から実行される。
appengineはよしなにスケールするため、スピンアップのタイミングでそのリクエストがそのインスタンスに対して、1度だけやってくるというイメージ。

設定方法

app.yamlへwarmupの設定を追加する

Github にサンプルファイルをおいているが、以下のようにwarmupを実行するという設定を入力する必要がある。

ポイントは2箇所

まずは、warmupリクエストを使用しますよ。という設定が以下。

inbound_services:
- warmup

もう一つは、warmupリクエストは /_ah/warmup のパスへリクエストが送られるため、

- url: /_ah/warmup
  script: _go_app
  login: admin

という設定が必要となる。
_ah ってどういう意味?って思うかもしれないが、 app handler とかって意味とどこかで見たけど忘れたw
また、 login:admin と書かれている場合には外部からのアクセスが行えないため、外からいたずらされないためには忘れずに付けましょう。

Application側の設定

こちらも Githug にサンプルを置いていますが、
特に難しいことはなく、handlerの設定として

http.HandleFunc("/_ah/warmup", warmup)

のような実装を行い、warmup functionの中で初期化を行いたい処理を書くと良いです。
先程も書いたように通常のハンドラと同様に実装できますので、requestを使ってappengine contextの生成も問題なく行えます。

参考

公式ドキュメント

追記 : 落とし穴

warmupはどうやら絶対に実行してもらえるという感じのものでもないらしい。

Note that warmup requests are not guaranteed to be called. In some situations loading requests are sent instead: for example, if the instance is the first one being started up, or if there is a steep ramp-up in traffic. However, there will be a "best effort" attempt to send requests to already warmed-up instances if warmup requests are enabled

意訳 : warmupの実行は保証してないよー、ベストエフォートだよー

つまり、warmupが実行される前にリクエストを受け取った際には、初期化がされているか判別して初期化する、のような制御が必要

こちらの資料の p.12 - p.13 あたりに特に詳しく説明が載っています。(ありがたい)

www.slideshare.net

enjoy GAE/go!