今まで普通に動いていたApacheが起動に失敗した(No space left on device)

状況確認

EC2上で稼働していたhttpdがある日突然起動に失敗した。
エラーログdefault:/var/log/httpd/error_logを確認のところ、次の一文が確認された

No space left on device: AH00023: Couldn't create the watchdog-callback mutex

検索すると、ログローテーション時にセマフォを使い切っていて起動できなかった模様。
現在のセマフォ状況はipcs -sコマンドで確認できる

[root@test-server]# ipcs -s
------ セマフォ配列 --------
キー     semid      所有者  権限     nsems
0x00000000 48365568   apache     600        1
0x00000000 48398337   apache     600        1
0x00000000 48431106   apache     600        1
0x00000000 48463875   apache     600        1
0x00000000 48496644   apache     600        1
0x00000000 48529414   apache     600        1

セマフォの上限はsysctl -aコマンドで確認できる

[root@test-server]# /sbin/sysctl -a | grep sem
kernel.sem = 250        32000   32      128

おそらくセマフォの識別子数が128で、ipcsコマンドの結果が128行に達すると起動に失敗する。

初期対応

service httpd restartでセマフォ数は減って起動するようになったが、翌日また増えている。
どうやらservice httpd gracefulコマンドだったりログローテーションの場合のみ、セマフォが増え続ける模様

調査

以下確認のところ、Apache/2.4.25で発生するバグであった模様
http://forum.directadmin.com/showthread.php?t=54265
https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x/CHANGES

恒久対応

バージョン確認すると、ばっちり該当した

[root@test-server]# httpd -v
Server version: Apache/2.4.25 (Amazon)
Server built:   Jan 19 2017 16:55:49

yum update httpdで2.4.27にアップデートし、問題が解消されたことを確認した

続きを読む

OpenFaaSを使ってGo言語でFunctionを書いて、AWSに展開したDocker環境にデプロイするまで

OpenFaaSは聞いたことがあるでしょうか。
まだ生まれて半年ほどしか経っていませんが、GithubStar数は6000を超える勢いで成長している有望なフレームワークです。

2017年10月18日に行われたServerless Meetup Osaka #4でOpenFaaSのことを聞いたので早速試してみました。

どこまでやるかというと、
OpenFaaSのAuthorであるAlex Ellisのブログで紹介されている、OpenFaaSでGo言語の関数を動かして、さらにクラウドに展開するところまでやってみたいと思います。

今回やってみて、日本語の情報がほぼ皆無で、英語でも情報がかなり少なかったので、丁寧に手順を載せておきます。

OpenFaaSいいところ

まとめとして先に良かったことを書いておきます。

  • Dockerイメージになるなら何でも動かせる
  • とはいえ、メジャーな言語のテンプレートを用意している
  • 手軽さは既存のFunction as a Serviceと変わらない

スケール・コストなどの観点はチュートリアルでは評価しきれないので、言及しません。

Faas CLIのインストール

Docker CE 17.05以上がインストールされていることが必要です。

コマンドでさくっとインストールできます。

curl -sSL https://cli.openfaas.com | sudo sh

brewコマンドが使えるなら、

brew install faas-cli

Selection_002.png

OpenFaaSデプロイ環境をローカルに追加

Kubernetesに比べると簡単に扱えると感じたDocker Swarmにデプロイします。

まずはDocker Swarm自体の初期化して、managerとworkerを動作させます。

docker swarm init

以下のコマンドでFaaSスタックをデプロイします。

git clone https://github.com/openfaas/faas && \
  cd faas && \
  git checkout 0.6.5 && \
  ./deploy_stack.sh

このデプロイした環境はどこにいるかというと、Docker Service (Swarm Mode) として動いているので、docker service lsのコマンドで確認できます。

image.png

ローカルのデプロイ環境にはhttp://localhost:8080でアクセスできるので開くと、次のようなFunction管理ポータルが表示されます。
Linuxの場合はhttp://127.0.0.1:8080で開く必要があるかもしれません。

Selection_006.png

Go言語のインストール

こちらの公式インストールガイドを参考にインストールしてください。

v1.8.3かそれ以降が必要です。
gvmでインストールしても問題ありません。

Selection_003.png

$GOPATHが設定されているか確認しておきます。
Selection_004.png

OpenFaaSプロジェクトの生成

まずはプロジェクトフォルダーを作成します。

mkdir -p $GOPATH/src/functions
cd $GOPATH/src/functions

続いてFaaS CLIを使ってプロジェクトテンプレートを生成します。
名前は参考記事通りgohashです。

faas-cli new --lang go gohash

Selection_005.png

プロジェクトの中身を確認する

gohash.ymlファイルにはテンプレートで作成されたFunctionとローカルの実行環境についての設定が書き込まれています。

gohash.yml
provider:
  name: faas
  gateway: http://localhost:8080

functions:
  gohash:
    lang: go
    handler: ./gohash
    image: gohash

gohash/handler.goにはHello Worldなコードが書かれています。

gohash/handler.go
package function

import (
    "fmt"
)

// Handle a serverless request
func Handle(req []byte) string {
    return fmt.Sprintf("Hello, Go. You said: %s", string(req))
}

そしてtemplate内には、各言語のテンプレートもありますが、Go言語のものがちゃんとあります。
main.goではOpenFaaSの仕様通り、 STDIN を受け取って、対応するFunctionを呼び出した結果を STDOUT に送るというシンプルな作りとなっています。

template/go/main.go
package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "os"

    "handler/function"
)

func main() {
    input, err := ioutil.ReadAll(os.Stdin)
    if err != nil {
        log.Fatalf("Unable to read standard input: %s", err.Error())
    }

    fmt.Println(function.Handle(input))
}

Dockerfileが各言語の環境ごとに用意されていて、ミニマルな実行環境を作成し、開発者が用意したFunctionコードをコンテナ内に格納してから最後にWatchdogを起動するという動きになっているようです。

WatchdogはGo言語で書かれた小さなHttpサーバーです。

template/go/Dockerfile
FROM golang:1.8.3-alpine3.6
# ---------- 略 -----------
WORKDIR /go/src/handler
COPY . .
# ---------- 略 -----------
CMD ["./fwatchdog"]

まずはデプロイしてみる

テンプレートを導入した時点で、環境構築が無事に完了しているか確認するために、HelloWorldのままデプロイしてみます。

faas-cli build -f gohash.yml
faas-cli deploy -f gohash.yml

BuildはDockerのイメージをダウンロードするところから始まるので、最初の実行は1分ほど待つかもしれません。

とくにエラーが表示されずに、
Selection_009.png
という風な表示がされたら成功です。

http://localhost:8080にアクセスしてgohashが追加されているか確認します。
左のFunction一覧からgohashを見つけたら適当なRequest bodyを打ち込んで INVOKE します。

Selection_007.png

これで無事にデプロイできることが確認できました。

Functionを実装していく

今回はgo1.9を使ったので、コンテナ内のgoも同じバージョンにしておきます。
今回のコードでは変更しなくても特に問題にはならないと思います。
コンテナ生成Dockerfileを書き換えてしまいます。

template/go/Dockerfile変更前
FROM golang:1.8.3-alpine3.6
template/go/Dockerfile変更後
FROM golang:1.9-alpine3.6

go言語のパッケージ管理ツールとしてdepをインストールします。

go get -u github.com/golang/dep/cmd/dep

そして、Goのデータ(Struct)からハッシュを生成するstructhashdepを使ってインストールします。
ただし、faas-cliがDockerコンテナをビルドするときに、template/go/をワーキングディレクトリとしてビルドを行うため、depの実行はこのディレクトリに移動して行う必要があります。

cd template/go/
dep init
dep ensure -add github.com/cnf/structhash

こうすると、Gopkg.lockGopkg.tomlvernder/template/go/以下に生成されます。
ちなみに、gvm環境だとdep ensureでライブラリがうまくインストールされないことがありますが、goのコードがビルドされるのはコンテナ内なので、一応そのまま進めても大丈夫です。

きちんと開発を進める場合はgvm linkthisdepがしっかりと使えるようにします。

それでは、受け取った文字列データからハッシュを生成するコードに変更しましょう。

gohash/handler.go
package function

import (
    "fmt"

    "github.com/cnf/structhash"
)

// S is sample struct
type S struct {
    Str string
    Num int
}

// Handle a serverless request
func Handle(req []byte) string {
    s := S{string(req), len(req)}

    hash, err := structhash.Hash(s, 1)
    if err != nil {
        panic(err)
    }
    return fmt.Sprintf("%s", hash)
}

プロジェクトのデプロイ

では、作成したコードをデプロイしてみましょう。

faas-cli build -f gohash.yml
faas-cli deploy -f gohash.yml

ポータルの実行結果はFunctionのデプロイで新しいものに置き換えたので結果が変わっています。

Selection_010.png

また、もちろんAPI URLも用意されているので、直接呼び出すこともできます。

curl -X POST -d "てすとめっせーじ" http://localhost:8080/function/gohash

Selection_011.png

テストを追加する

テンプレートのDockerfileにはgo testでテストを実施しているのですが、今のところテスト用のgoファイルは生成されないようです。

今は自分で作りましょう。

touch gohash/handler_test.go
gohash/handler_test.go
package function

import "testing"

func TestHandleReturnsCorrectResponse(t *testing.T) {  
    expected := "v1_b8dfcbb21f81a35afde754b30e3228cf"
    resp := Handle([]byte("Hello World"))

    if resp != expected {
        t.Fatalf("Expected: %v, Got: %v", expected, resp)
    }
}

テストを作ったところで、わざとgohash Functionを間違えて修正してしまいましょう。

handler.go修正前
hash, err := structhash.Hash(s, 1)
handler.go修正ミス
hash, err := structhash.Hash(s, 2)

この状態で再びビルドを実行します。

faas-cli build -f gohash.yml

ちゃんとビルド中にテストで失敗してくれます。
Selection_012.png

コードを元に戻してビルドが成功することを確認します。

DockerHubにビルドしたイメージをPUSH

リモート環境でOpenFaaSを動作させるためには、FunctionのDockerイメージをDockerHubまたは別のレジストリに登録しておく必要があります。

まずは、[DockerHubのユーザ名]/gohashとなるように、gohash.ymlを書き換えます。

gohash.yml書換前
    image: gohash
gohash.yml書換後
    image: gcoka/gohash

Docker Hubの登録が済んでいれば、

docker login

でログインし、

faas-cli push -f gohash.yml

でビルドしたイメージをPUSHします。

AWSにデプロイしてみる

AWS EC2コンソールで SSH KeyPairsを作成しておいてください。
ssh-addもお忘れなく。

ssh-add ~/.ssh/yourkey.pem

AWSにDockerをデプロイするためのテンプレートが用意されているので、ここにアクセスして、
Deploy Docker Community Edition (CE) for AWS (stable)をクリックします。
use your existing VPCを選択すると、Docker用ネットワークの設定をいろいろやらないといけなくなり、VPC内のネットワーク構築の知識が必要となるようです。

https://docs.docker.com/docker-for-aws/#docker-community-edition-ce-for-aws

以下の設定は環境に合わせて変更が必要です。

  • SSHキーにKeyPairsで作成したものを指定。

また、このテンプレートでのCloudFormation実行には、以下のCreateRoleが必要です。
正確な一覧はこちら

  • EC2 instances + Auto Scaling groups
  • IAM profiles
  • DynamoDB Tables
  • SQS Queue
  • VPC + subnets and security groups
  • ELB
  • CloudWatch Log Group

特に設定は変更していません。
デプロイを試すだけなので、Swarm ManagerとWorkerの数は1ずつにしました。

Selection_029.png

CloudFormationのStackデプロイが完了したら、デプロイ結果のOutputsタブから、Swarm Managerのインスタンスへのリンクが参照できるので、開きます。
Selection_030.png

Selection_031.png

このインスタンスにSSH接続を行います。
ユーザーはdockerを指定します。

ssh docker@54.159.253.49

OpenFaaSのスタックを導入するために、Gitが必要なので、インストールします。

中身はAlpine Linuxなので、apkコマンドでパッケージをインストールします。

sshコンソール
sudo apk --update add git

ローカルでにインストールしたときと同じコマンドですが再掲。

sshコンソール
git clone https://github.com/openfaas/faas && \
  cd faas && \
  git checkout 0.6.5 && \
  ./deploy_stack.sh

ではOpenFaaSスタックが無事デプロイされているか確認します。

AWSに構築したテンプレートはインターネットからはLoadBalancerを通してアクセスできるので、LoadBalancerのURLを調べます。DNS name: に表示されているものがそれです。

Selection_034.png

URLがわかったら、ポート8080を指定してアクセスします。

Selection_035.png

うまくいっていることを確認したらAWSにデプロイします。
--gatewayオプションを使えばgohash.ymlファイルを書き換える必要がありません。

faas-cli build -f gohash.yml --gateway http://your.amazon.aws.loadbalancer.url:8080

Selection_038.png

Selection_039.png

無事にAWSで動きました。

トラブルシューティング(詰まったところ)

faas-cli buildしてもコード変更が反映されない

--no-cacheをつけることで、すべてのビルドをやり直してくれます。

faas-cli build -f gohash.yml --no-cache

structhashパッケージがvenderディレクトリにコピーされない

$GOPATHが正しく設定されているか確認してください。

gvmを使ってgoをインストールしている場合は、$GOPATH/src/functionsgvm linkthisを実行すると解決するかもしれません。

AWS CloudFormationのデプロイに失敗する

use your existing VPCのテンプレートを使っている場合は、使わないでVPCの作成もDockerテンプレートに任せてください。

AWSにデプロイしたFunctionを実行しても500: Internal Server Errorになる

Docker HubにDockerイメージをPUSHしたか確認してください。

ちゃんと動いているかわからない・・・

docker serviceとして動いているのでdockerコマンドからいくつか情報を得ることができます。

指定したFunctionのプロセス動作情報を表示
docker service ps gohash
指定したFunctionのログを表示
docker service logs gohash
gatewayのログを表示
docker service logs func_gateway

最後に

Docker SwarmはMicrosoft Azure Container Serviceもサポートしているのですが、完全なマネージドの場合OpenFaaSが必要とするDockerバージョンが足りていないようです。

今回はDocker Swarmに対してデプロイしましたが、Kubernetesもサポートしているので、そちらも試してみたいと思います。
Kubernetesの場合は、Swarmよりもホスティング対応しているクラウドがいくつかあるようなので、期待が持てますね。

情報がとても少ないので、どんどん試してどんどん情報公開していきましょう!

続きを読む

40分くらいで node.jsインストール -> ServerlessFramework -> lambda から Slackに通知まで

お仕事でぼちぼちServerlessFrameworkを使い始めました。

辛かった

lambdaを始めたばかりの時は「いやー、フレームワークとかまだ早いっしょ」とか「もうちょっとWeb UIで慣れてからやろう」と思っていましたが、
CLIでデプロイできるのはやっぱ楽だし、超早いです。 API gatewayとlambdaの連携の設定とか面倒だし。zipを固める作業とか苦行でした。その先になにかあるかなー、と思っていたんですが。

で、ServerlessFrameworkですが、使い始めてみると

「ちょっとコレなしにデプロイとか、する気おきないなー」とか「これ取り上げられたら、仕事休みたいなー」という程度に普段使いするツールになりました。

という訳で、私がServerlessFramework導入するまでをまとめてみます。
私は下記の状況で使っています。

  • ndenvでバージョン指定してインストール
  • node関連はシステムワイドにインストールせずに作業ディレクトリのnode_modules配下のものを使う
  • ローカル実行したい( テストを手軽に実行したい… )ので lambda-local というツールをインストール
  • slackにWebhookの設定

nodeインストール

ここを参考にさせていただきながらインストールしました! -> http://qiita.com/MahoTakara/items/8fdebe32e8f326afa7f8

bashの場合
git clone https://github.com/riywo/ndenv ~/.ndenv
echo 'export PATH="$HOME/.ndenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(ndenv init -)"' >> ~/.bash_profile
exec bash -l
git clone https://github.com/riywo/node-build.git ~/.ndenv/plugins/node-build
ndenv install -l
AWS_lambdaのnode.jsのバージョン、v4.4.3をインストール
ndenv install -l | grep '4.3'
ndenv install v4.4.3
ndenv global v4.4.3
バージョン確認
ndenv versions
バージョン確認、結果
  system
* v4.4.3 (set by /Users/kaoru_inoue/.ndenv/version)

ServerlessFrameworkのプロジェクト作成

mkdir testservice
cd testService
npm_initでpackage.jsonを作成
npm init
下記のように質問に答えた
name: (testService) servicename
version: (1.0.0)
description: 検証用サービス
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/kaoru_inoue/Documents/workspace/testService/package.json:

{
  "name": "servicename",
  "version": "1.0.0",
  "description": "検証用サービス",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes)

するとローカルにpackage.jsonというファイルができます。
これはnodeで、このアプリに必要なモジュールなどを一気にいれたり、テスト( すみません、まだできておりません… )を設定したりするファイルです。
モジュールを一気に入れる機能、超便利です。

動作に必要なモジュール、開発に必要なモジュールのインストール
npm install --save slack-node
npm install --save-dev serverless aws-sdk lambda-local

–save アプリの動作に必要なモジュール
–save-dev 開発に必要なモジュールです

ServerlessFramework は開発には必要ですが、lambda側にアップする必要はないのでこちらですね。
あとnodeモジュールの「aws-sdk」はlambda側に予め入っているので、デプロイ時は同梱しなくてもok

下記のようにpackage.jsonが書き換わります。
npm installコマンドは package.jsonという名前のファイルを元に必要なモジュールをインストールします。
ので、package.jsonを作業ディレクトリにコピーして npm install で一発でインストールでも良いです( 同じようなサービスを複数作る場合は雛形みたいなものがあった方が楽そうですね )

package.json
{
  "name": "testservice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "babel-polyfill": "^6.23.0",
    "slack-node": "^0.1.8"
  },
  "devDependencies": {
    "aws-sdk": "^2.28.0",
    "lambda-local": "^1.4.2",
    "serverless": "^1.9.0"
  }
}

slackに通知するコードです( 下記サンプルの webhookUriは書き換える必要があります! )
ファイル名は「slackTest.js」とします( 任意ですが、後でServerlessFrameworkでデプロイするファイルを指定します )

slackTest.js
'use strict';

const AWS = require('aws-sdk');
const Slack = require('slack-node');
const slack = new Slack();

const webhookUri = 'https://hooks.slack.com/services/naisho_himitsu';
slack.setWebhook(webhookUri);

function pushSlackChannel(channel, username, text ){
  return new Promise(function(resolve,reject) {
    slack.webhook({
      channel: channel,
      username: username,
      text: text
    }, function(err, response) {
      if (err) {
        console.log(response);
        reject(new Error('Error'));
        return;
      } else {
        resolve(response);
      }
    });
  });
}

module.exports.slackPost = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'slack post in success',
      input: event,
    }),
  };
  pushSlackChannel('#general', 'tin-machine', 'こんにちは!')
    .then((response) => {
      // console.log(response);
      callback(null, `Successfully ON watchdog stream.`);
    })
    .catch(err => {
      // console.log(err);
      callback('Error occured.');
    });
};

slackアカウント作成( 5分程度? )

上記のコードで指定すべきWebhookURIがありません、はりきってSlackアカウントを作成しましょう( 5分程度 )

メールアドレスが必要です( 私は hogehoge+1@gmail.com みたいなアドレスで登録しました )
* https://slack.com/ にアクセス、メールアドレス入れて[ create new team ]
* メールに確認用の数字が届くので入力
* あとはグループ名を決めたり、招待する人を決めたり

slackのwebhook urlをコピペ

画面右上のアカウント名をクリック

スクリーンショット 2017-03-17 20.11.05.png

[ Apps & integrations ]をクリック

スクリーンショット 2017-03-17 20.11.21.png

上部の[ Manage ]をクリック

スクリーンショット 2017-03-17 20.11.36.png

[ incomming webhooks ]で検索

スクリーンショット 2017-03-17 20.13.31.png

[ Add Configuretion ]をクリック

スクリーンショット 2017-03-17 20.13.54.png

投稿するチャンネルを設定

スクリーンショット 2017-03-17 20.14.23.png

[ Webhook URL ]の[ Copy URL ]をクリックしてWebhook URLをコピー

スクリーンショット 2017-03-17 20.15.29.png

lambda-local でローカルでテスト

lambdaは何かイベントで発動しますが、適当なダミーのデータを作成します。

ダミーのイベントファイルを作成します
mkdir event-samples
vi event-samples/testEvent.json
testEvent.jsonの内容
{
  "key3": "value3",
  "key2": "value2",
  "key1": "value1"
}
lambda-localを実行します
./node_modules/lambda-local/bin/lambda-local -l slackTest.js -h slackPost -e event-samples/testEvent.json

ローカルで動きましたかね? で、これをサーバレスで動かしたい訳です。

ServerlessFrameworkでデプロイ!

ServerlessFrameworkでデプロイしてみます。
これをお読みの方はAWSのWebUIで書いたり、zipに固めてアップしたりといった苦行をされている方だとお見受けします。そろそろ楽になっていいはず!
まずは簡単に『時刻で実行されるlambda関数』を設定してみます。 ひとまずリスクが少ないバッチ処理などをlambdaで書いてみるか、といった需要はあるはず。
時刻の指定は日本時間ではなく『UTC』です。いつも混乱しますが、AWS Lambda – Scheduled EventのCron書式を参考にさせていただきつつ、
直近の時刻を指定してみましょう。 日本の方がUTCに対して9時間すすんでいるので… 9時間、引きます。 今が21時なので、echo $((21 – 9)) で 12時ですね。

ServerlessFrameworkでのデプロイを設定
vi serverless.yml
serverless.ymlの内容
service: slackPostService
provider:
  name: aws
  runtime: nodejs4.3
  iamRoleStatements:
    - Effect: Allow
      Action:
        - cloudwatch:*
      Resource: "*"

package:
  exclude:
    - jsconfig.json
    - event-samples
    - package.json
    - node_modules/**
    - '!node_modules/aws-sdk/**'
    - '!node_modules/slack-node/**'

functions:
  slackPostFunction:
    handler: slackTest.slackPost
    events:
      - schedule: cron(5 12 ? * MON-FRI *)

いざデプロイの前に AWS CLI アカウント設定

すみません、pythonのパッケージ管理ツール pipのインストールと、AWSアカウント取得は既に設定ずみとさせてください。これ結構時間かかった記憶があります( が、持ってますよね…? )

AWSのアカウント情報を入力します。AWSにログインし、一番上の[ サービス ]->[ IAM ]->左側の[ ユーザー ]->使えるユーザ名をクリック->[ 認証情報 ]のタブ->[ アクセスキーの作成 ]
ここで作成された
* アクセスキー ID
* シークレットアクセスキー
をメモします。すぐ使います

aws-cliをインストール

pip install --upgrade --user awscli
aws configure
現れるダイアログで入力
AWS Access Key ID [None]: アクセスキー ID
AWS Secret Access Key [None]: シークレットアクセスキー
Default region name [None]: ap-northeast-1
Default output format [None]:

デプロイ

上記で設定したAWSアカウントでデプロイします。

デプロイ!!
./node_modules/serverless/bin/serverless deploy --region ap-northeast-1 --stage production

Slackへの通知きましたでしょうか? 

これにプラスしてAWS node.jsライブラリと組み合わせて使うと、
EC2を定期的に立ち上げたり、spotfleet群を立ち上げたりできます。

ささっとコピペでいけるようにまとめてみました。 意外と手軽に始められますよServerlessFramework。

次はAWS API Gatewayとの連携をまとめてみます!
( 冒頭にも書きましたが、あれ手動でやると心折れそうになるくらいつらい、なんでlambdaとAPI gatewayの画面いったり来たりしないといけないんだ、という )

続きを読む