AWSを使い倒す(15.Step Functions)

lambdaとAPI Gatewayの組み合わせが動いたと思った矢先、

思わぬところで問題発生。

API Gatewayってタイムアウト29秒までしか設定できないのね。

lambdaで普通に1分ぐらいかかる処理仕込んでたので

API Gatewayが待ちきれない状態になってしまった・・・。

 

【API Gatewayタイムアウト対策】Step Functionsを組み合わせて非同期処理にしてみる | Developers.IO

ということでこのStep Functionsを使って解決を試みる。

記事を読むにStep Functions(のステートマシン)を介してlambdaを実行することで、

lambda実行中なら実行中、終わったら結果をステートマシンが代わりに「応答」してくれるようになるので、API Gatewayタイムアウトが回避できる。

代わりにAPI叩く人は結果返ってくるまで何回かAPIを叩かないといけないって感じ。

 

 

 

大変なのが

APIを叩いた時にユーザが渡してくるパラメータを

一度ステートマシンが受け取って

それをlambdaに渡す

ってしないといけないのでややこしい。

ここの理解にめちゃめちゃ時間がかかった。

 

 

まずは適当なlambda関数を作成する。

import json
def lambda_handler(event, context):
    msg = "bucket="+ event["s3bucket"]+",folder="+event["s3folder"]
    return msg

 eventとしてStep Functionsから引数を受け取れるか、の確認するだけなので

本当にシンプルな作り。

 

次にStep Functionのステートマシンの作成

f:id:Messerarche:20200710211005p:plain

 

作成画面

f:id:Messerarche:20200710211830p:plain

コードスニペットで作成、タイプは標準を選ぶ

 

定義を書く。

f:id:Messerarche:20200710211910p:plain

ここにどう書くか、を理解するのが大変だった

 

 

今回書いたのはずばりこのコード

{
  "StartAt": "Test",
  "States": {
    "Test": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:[中略]:function:test-lambda",
      "Parameters": {
     "s3bucket.$": "$.bucket",
     "s3folder.$": "$.folder"
    }, "End": true } } }

 大事なのは、ResourceとParameters

 

Resourceには、lambda関数のarnを記載する。

 

Parametersは記法に戸惑うが、API Gatewayから変数として受け取るのでこう書く。

具体例を書くと、以下のようなフォーマットでAPI Gatewayから情報が来た時に

"input": {
    "bucket: "nameofbucket",
    "folder": "nameoffolder"
}

 

"s3bucket.$": "$.bucket"の意味は

s3bucketは変数です、変数の中身は来た情報の中のbucketが指し示す値です

ということになる

上記のような解釈で今度はステートマシンが

"event": {
    "s3bucket": "nameofbucket",
    "s3folder": "nameoffolder"
}

と情報を再構築してlambdaに渡してくれる。

これを元にlambdaは

event["s3bucket"]

として値を受け取ることができる。

$.だの.$だのをつけないと、変数ではなく定数と解釈されて受け取れなくなる。

 この辺の何がどうなるはこちらで試されている

StepFunctionsの入力方法サンプルメモ集 - Qiita

 

 

作成すると、ARNが表示されるのでメモっておく。

f:id:Messerarche:20200711013205p:plain

 

 

API Gatewayの前にAPI Gateway用のIAMロールを作成しておく。

具体的には、StepFunctionsの実行権限を持ったロールが必要。

API Gateway を使用して Step Functions API を作成する - AWS Step Functions

 

 

ロールを作成したら、API Gatewayに移動

f:id:Messerarche:20200711014201p:plain

な形でメソッドとリソースを作成する。

executionがlambdaをキックするAPI、

statusがlambdaの進捗を確認するAPI。

 

メソッドの作成時に、StepFunctionsを選択する

f:id:Messerarche:20200711014610p:plain

重要なのはアクションのところ。

LambdaをキックするAPIにはStartExecution

進捗を確認するAPIにはDescribeExecution

と入力する。

実行ロールはさっき作ったIAMロールのarnを入力。

 

出来上がったら早速テストをする。

まずはlambdaの実行、つまりexecutionのPOSTから、テストの画面を開いた後

リクエスト本文に以下を入力

f:id:Messerarche:20200711015133p:plain

 

黒塗りのところにステートマシンのarnを記載する。

また、ステートマシンに渡したい変数を図のように記載する。

そしてテスト実行。

 

 

実行されたら

f:id:Messerarche:20200711015547p:plain

こんなレスポンスが返ってくる。

このexecutionArnを使って進捗確認を行うのでメモしておく。

 

次に進捗確認のほうのメソッド(/status)にうつる

リクエスト本文に、先ほどのexecutionArnを図のように記載する

f:id:Messerarche:20200711015918p:plain

これで実行すると、

先ほどキックしたlambdaが実行中であれば実行中、

実行完了していたら処理結果が返ってくる。

 

その進捗具合はステートマシンのログから確認ができる。

うまくいっているとこんな感じで

f:id:Messerarche:20200711020058p:plain

 

1.ExcecutionStartedにはAPIGatewayから送られてきた情報が

f:id:Messerarche:20200711020556p:plain

 

3でステートマシンがlamdaに渡した情報が

5でlambdaの実行結果が見える

f:id:Messerarche:20200711020947p:plain

 

一応この通りやれば動くハズ。

 

 

 

ここからは追加で

リクエスト本文にいちいちステートマシンのarn情報を記載しない方法と、

パスパラメータをステートマシン越しにlambdaへ渡す方法を記す。

 

まずは、実行したいステートマシンをあらかじめ記載しておく方法

f:id:Messerarche:20200711021539p:plain

この画面の統合リクエストのところをクリック

 

開いた画面の一番下、マッピングテンプレートを開く

ここにいろいろ書くことで、リクエスト本文の内容とかを書き換えることができる。

公式ドキュメントを参考に

API Gateway を使用して Step Functions API を作成する - AWS Step Functions

{
    "input": "$util.escapeJavaScript($input.json('$'))",
    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld"
} 

こんな感じで記載する。

stateMachineArnはもちろん自分のステートマシンに合わせて書き換える

右下の保存を押す。これでAPI実行の度にステートマシンの情報を書かなくてもよくなる。

 

 

 

次はパスパラメータ

f:id:Messerarche:20200711022354p:plain

パスパラメータを使うためのリソースとメソッドを作成する。

 

テストを押した時にここに

f:id:Messerarche:20200711022445p:plain

(めちゃ適当に)値を入れて、これがlambdaまで伝われば成功

 

こちらも統合リクエストでマッピングテンプレートの設定を行うことで実現できる

#set( $body = $util.escapeJavaScript($input.json('$')) )
{
  "input": "{\"input\":$body,\"InputParams\":{\"bucket\":\"$input.params('bucket')\",\"folder\":\"$input.params('folder')\"}}",
"stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld" }

ずばり書くとこんな感じ。

参考にしたのは以下

API GatewayでStepFunctionsと統合する時のTips - Qiita

API Gateway マッピングテンプレートとアクセスのログ記録の変数リファレンス - Amazon API Gateway

 

 

これをテストすると、API Gatewayからの出力はこうなる

"input": {
    "input": {},
    "InputParams": {
        "bucket": "a",
        "folder": "b"
    }
},

 

パスパラメータで引数を渡すし、実行時のステートマシン設定も不要になったので

テスト時にリクエスト本文に何も書く必要がなくなったため、

"input": {}、つまり記事の前半で参照していたinputには何も入らない。

代わりに、InputParams配下に自分の指定したパラメータが入っている。

 

 

ステートマシンにも変更が必要。

今まで受け取っていたinputの中身が空っぽになって、

代わりにInputParams配下に中身が入るので、そのような宣言がいる。

{
  "StartAt": "Test",
  "States": {
    "Test": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:[中略]:function:test-lambda",
      "InputPath": "$.InputParams",
      "End": true
    }
  }
}

 

InputPathで宣言してやる。以下が参考

InputPath およびパラメータ - AWS Step Functions

 

lambdaも実態に合わせて変更("s3bucket"とかをただの"bucket"にしただけ)

import json
def lambda_handler(event, context):
    msg = "bucket="+ event["bucket"]+",folder="+event["folder"]
    return msg

 

これで、パスパラメータからステートマシンに、ステートマシンからlambdaに変数を渡していく仕組みが動く。

 

 

動きは確認できたので、あとは実装だ・・・!

 

AWSをいじり倒す(14.API Gateway)

API Gatewatを使って、lambdaに変数を渡せるAPIを作ってみる

今回の目標は、

API Gatewayで用意したURLを叩くと

s3の指定したバケットに指定した名前のフォルダができるもの」を作る

 

先にlambda関数を作成する。

 コードはこんな感じ。
import json
import urllib.parse
import boto3

s3 = boto3.client('s3')


def lambda_handler(event, context):
    bucket=event['pathParameters']['bucket']
    folder=event['pathParameters']['folder']

    response = s3.put_object(
        Bucket=bucket,
        Body='',
        Key=folder
    )

pathParametersでバケット名とフォルダ名を受け取るところが肝。

ちなみにバケットを新しく作るコードではない。

あくまで指定したバケットにフォルダを作るだけなので、バケットは先に作っておく。

 

まずはテストをしてみる。テストイベントの設定から

f:id:Messerarche:20200709233645p:plain


イベントテンプレートのAmazon API Gateway AWS Proxyを指定する

f:id:Messerarche:20200709233750p:plain

 

出てきたイベント内容の、pathParametersにbucketとfolderの情報を書き込む。

API叩いた時に飛んでくるリクエストの中身をここに書いて模擬できるというわけだ。

変更する必要がある箇所は2箇所

f:id:Messerarche:20200709234402p:plain

まずはresouceとpathのところ。

https://example.com/slp/[バケット名]/[フォルダ名]

みたいな感じでAPIを作りたいのでそう書く。

pathのところには[バケット名]と[フォルダ名]を実際の値で埋めたものを書く

 

もう一つはここ、pathParameters

f:id:Messerarche:20200709234812p:plain

lambdaに渡す変数を指定する。さっきのコードで

bucket=event['pathParameters']['bucket']

folder==event['pathParameters']['folder']

のところで受け取る値となる

 

作り終えたらテスト実行。

確認しに行くと・・・できてるが、フォルダじゃなくてファイルになってる・・・

f:id:Messerarche:20200709235732p:plain

フォルダを作るには、

test-folder-12345/ みたいに最後にスラッシュを付けないといけないようだ。

ということで

folder = folder +"/"

をくっつけてリトライ

 

f:id:Messerarche:20200710000513p:plain

うむ。

 

では本題のAPI Gateway

 

参考:ゼロから作りながら覚えるAPI Gateway環境構築 | Developers.IO

 

f:id:Messerarche:20200709222411p:plain

Websocketはチャットみたいな頻繁にやり取りするものに使うとして

HTTPとRESTはどっちがいいのか

Amazon API Gatewayは「HTTP API」と「REST API」のどちらを選択すれば良いのか? #reinvent | Developers.IO

勉強という意味では機能の多いRESTで十分に慣れたほうがよさそうかな。

 

早速設定してみる

f:id:Messerarche:20200709224055p:plain

REST、新しいAPIを選択。

API名も適当につける。

 

いろいろ設定できそうな画面ができてきた。

f:id:Messerarche:20200709224348p:plain

 

 

リソース=ディレクトリみたいなもの。

例えばtestリソースを作るとAPI叩く時のURLが

https://example.com/test

になる。

 

アクションからリソースの作成を選択した画面

f:id:Messerarche:20200709224941p:plain

 リソース名とパス名で別のものを付けられるようだけど、別にすると混乱しそう

 

なお、 階層構造で作ることができる。

f:id:Messerarche:20200709230255p:plain

 

 

次に、作りたいフォルダ名をlambdaに変数として渡したいので

パスパラメータの設定をする

AWS Lambda + API Gateway で/hoge/{group}/{user}のように階層構造のREST APIでパスパラメータの受け渡し – 或る阿呆の記

設定自体は{}でくくるだけ

f:id:Messerarche:20200709231012p:plain

こんな感じ。バケット名とフォルダ名を受け取るつもり

f:id:Messerarche:20200709231351p:plain

 これでhttps://example.com/slp/mf/[バケット名]/[フォルダ名]

な感じのURLを叩けばバケット名とフォルダ名がlambdaに渡されるようにする

 

次にメソッドの作成

 メソッドでHTTPのメソッドを指定できる。

 

f:id:Messerarche:20200709231528p:plain

やりたい処理に対してどれを選べばいいんだよ?ってなるかもしれないが

大まかに書くと、

GETがリソースの取得

POSTがリソースの新規登録

PUTが既存リソースの更新

DELETEがリソースの削除

という感じ。

とは言ってもAPIは多種多様でこれはこれと定めるのは難しい。

有名サービス間でも使い方が微妙に違っていて

こう使うべしみたいな論で本が一冊出てるレベル。

O'Reilly Japan - Web API: The Good Parts


今回は新しいフォルダを作る、なのでPOSTかな。

POSTを確定すると連携先のlambda関数の指定が出てくる

f:id:Messerarche:20200709232330p:plain

統合タイプはもちろんLambda関数

Lambdaプロキシ統合の使用にチェック。

これでパスパラメータが使えるようになる

Lambda関数に事前に作っておいた関数を指定する

 

設定するとこんな画面が出てくる

f:id:Messerarche:20200710001409p:plain



テストを押すと

f:id:Messerarche:20200710001025p:plain

おー、lambdaでやったテストと同じことができそう。

早速、パスのところのfolderとbucketに値を入れてテスト

他の場所に値を入れる必要はなし

 

結果・・・怒られた。

f:id:Messerarche:20200710002000p:plain



 

API Gateway コンソールを使用してメソッドをセットアップする - Amazon API Gateway

バックエンドが返したレスポンスに対応するメソッドレスポンスが定義されていない場合、API Gateway はクライアントにレスポンスを返しません。代わりに、500 Internal server error エラーレスポンスを返します。

 

確かに、lambdaから応答を返す設定は何もしてないので、怒られて当然か。

 

S3を確認するとちゃんとfolderはできていた。

lambda関数はちゃんと動いているようだ

f:id:Messerarche:20200710001833p:plain

 

というわけでlambda関数にレスポンスのコードを書く。

Amazon API Gateway + AWS Lambda でのレスポンス形式 – サーバーワークスエンジニアブログ

基本的にはstatusCodeをreturnするだけでよいみたい。簡単〜

 

応答処理を入れたバージョン。

import json
import urllib.parse
import boto3

s3 = boto3.client('s3')

def lambda_handler(event, context):

    bucket=event['pathParameters']['bucket']
    folder=event['pathParameters']['folder']
    folder = folder +"/"

    try:
        response = s3.put_object(
            Bucket=bucket,
            Body='',
            Key=folder
        )
        
        print(response)
        print(response['ResponseMetadata']['HTTPStatusCode'])
        return {
            'statusCode': response['ResponseMetadata']['HTTPStatusCode'],
            'body': json.dumps('Success to make folder')
        }
    except Exception as e:
        print(e)
        return {
            'statusCode': 500,
            'body': json.dumps(e.response['Error']['Code'])
        }


うまくいったらput_objectのresponseに書かれているステータスコードを(200だろうけど)をstatusCodeとしてreturn。

失敗したらstatusCodeを500と

エラーメッセージを取り出して返却するようにしてみた。

 

ではテスト

まずは成功事例、実在するバケットを指定して実行

f:id:Messerarche:20200710015044p:plain

うむ。

 

次に失敗事例として、バケット名を存在しないものにして実行

f:id:Messerarche:20200710015143p:plain

思惑通り動作した。

 

最後にデプロイを行う。

デプロイを行うと外部に公開されることとなるが、

誰からでもアクセスできてしまうので認証を入れ込む

 

APIキーを作る。

f:id:Messerarche:20200710022618p:plain

 

作成画面はいたってシンプル

f:id:Messerarche:20200710022718p:plain

 

できた。表示のところを押すとキーが表示される

f:id:Messerarche:20200710022924p:plain


API設定に戻ってきて、メソッドリクエストを選択

f:id:Messerarche:20200710023130p:plain

APIキーの必要性をtrueに設定

f:id:Messerarche:20200710023252p:plain

 

終わったらいよいよデプロイ

f:id:Messerarche:20200710023323p:plain

 

ステージ名devで作成

f:id:Messerarche:20200710023422p:plain

 

devステージにURLが出現。デプロイはこれで完了。

f:id:Messerarche:20200710023613p:plain

 

 

次に使用量プランを通じてAPIキーを設定する

f:id:Messerarche:20200710023719p:plain

 

スロットリングとクォータのチェックを外す。

どちらもAPIの呼び出し制限に関する設定だ

f:id:Messerarche:20200710023839p:plain

 

APIステージとの関連付け。さっきのdevのこと

f:id:Messerarche:20200710023940p:plain

追加するとこんな感じ。

f:id:Messerarche:20200710024051p:plain

 

次でAPIキーの紐付け

f:id:Messerarche:20200710024207p:plain

さっき作ったAPIキーの名前を入力して追加。

 

ではいよいよ実行。

どのURLにアクセスすればいいかはlambdaの画面から確認ができる。

f:id:Messerarche:20200710030130p:plain

キーまで全部丸見え。

 

 

では手元のPCから実行してみる

$ curl -X POST -H 'x-api-key:[APIキー]' https://[URL]/dev/slp/mf/[bucket名]/[folder名]

"Success to make folder"

  

できてるできてる。

f:id:Messerarche:20200710025921p:plain

 

APIキーのところを抜いて叩くとエラー

{"message":"Forbidden"}

 

ちゃんと機能してる。

ということで無事完成!

 

lambda完全に理解した

画像


 

今までの軌跡。

構成図をアイコンも使ってカッコよくしてみた

f:id:Messerarche:20200710043349p:plain

 

 

 

2020年春アニメおすすめ

NETFLIXもしくはAmazonPrimeVideoでみることができるアニメオンリーで

個人的におすすめと思ったものまとめ。

2020/4〜6放送分。

 

絶対みるべき

kakushigoto-anime.com

久米田さんといえば世の中の風刺で有名だが

いつものキレはそのままにほんのり感動を混ぜた良作。

序盤から意味深な伏線をふわっと残すので、気になって最後まで楽しく見れた。

 

 

kaguya.love

当然1期から見るのをおすすめ。

安心して見られるギャグ+ラブコメ。見る清涼剤。

声優の声色の使い分けっぷりもすごい

3期もあるらしいという噂

 

 

www.ghostintheshell-sac2045.jp

 

見た目でめちゃめちゃ損してると思った。

ガチガチの3Dを我慢して見ると、やはりストーリーは攻殻で引き込まれる。

ネットリンチとか割と身近に問題になってる話も織り交ぜつつ

キャラ同士の掛け合いは相変わらずの面白さ。

3Dならではのカチカチなアクションも、

ある意味AIにサポートされた機械的な動きにマッチしていてアリかなとすら思えてくる

 

 

見ても良いと思う

hamehura-anime.com

 

乙女ゲーが舞台ということでどうにも最初は気乗りしなかったが、

見ていくうちに慣れてきて、だんだん面白くなり、最後も綺麗に終わった。

悪役令嬢というジャンルを理解するため教養として見ておくべき、という側面もある

 

 

singyesterday.com

恋愛のヤキモキ感も見ていてもどかしい、そのもどかしさを楽しむアニメ。

キャラもよく、人間臭い悩みも身に滲みる。

ただ、アニメにすると会話の中で大事な「間」がどうにもテンポの悪さに感じてしまった。

 

 

ネタとして

hachinan-anime.com

OPだけは聞いた方がよい。

デーモン閣下×宝野アリカってだけでヤバい。

www.youtube.com

サムネがすでにヤバい

AWSをいじり倒す(13-3.lambda+EFS)

あらすじ

lambdaを使おうとするもtensorflowがデカすぎて容量問題に苦しむ

そこに現れた一筋の光、新機能EFSとの接続

新機能 – Lambda関数の共有ファイルシステム – Amazon Elastic File System for AWS Lambda | Amazon Web Services ブログ

さっそく試してみることにする。

 

 VPCを作成

まずはVPCを作成。インターネットゲートウェイの設定も作成。

後々s3も使おうと思っているのでエンドポイントも作成。

この辺は過去記事で一回やっているのでサクサク作れた

サブネットは2個作っておくと良いらしいので2個作った。

 

セキュリティグループを作成

1つはインバウンドやアウトバウンドになにも書いていないlambda-sg

f:id:Messerarche:20200703230100p:plain

 

もう一つはEFSにアタッチするためのlambda-efs-sg

こちらはインバウンドでNFSの2049を許可する

f:id:Messerarche:20200703230153p:plain

 

あとはEC2にアタッチするSSHが許可されたSG

まあこれはおそらく既存のものがあるはず

 

 

次にEFSを準備

さっき作ったVPCを指定し

セキュリティグループはlambda-efs-sgを指定

f:id:Messerarche:20200703230629p:plain

 

もう一つ設定が必要なのはアクセスポイントのところ

f:id:Messerarche:20200703173443p:plain

lambdaにEFSの操作権限を与える。

このまま作成を完了

 

lambdaの設定

次にlambdaの関数画面へ

f:id:Messerarche:20200703173901p:plain

アクセス権限を設定する

 

ロール名をクリックしてIAMの画面を開き、以下の二つのポリシーをアタッチ

f:id:Messerarche:20200703174111p:plain

 

 

 

次にlambda関数のVPCの設定へ

f:id:Messerarche:20200703171629p:plain

 

f:id:Messerarche:20200703171706p:plain

ここのサブネットで作った2つともを登録

セキュリティグループはlambda-sg

で作成。

ちょっと時間がかかって、できましたのメッセージ。

lambdaが裏で動かすEC2にネットワークインタフェースを作ったりなんやりしてたみたい

f:id:Messerarche:20200703174512p:plain

 

 

次にいよいよファイルシステムとの接続

f:id:Messerarche:20200703174626p:plain

 

さて・・・

f:id:Messerarche:20200703174739p:plain

EFSファイルシステムってダブルミーニング

 

f:id:Messerarche:20200703174909p:plain

EFSを選んだら、マウントするターゲットポイントが出てきた。

さっきEFSで設定したアクセスポイントだ。

 

ローカルマウントパスには適当に

/mnt/slp

と設定。

 

EFSへのパッケージインストール

さて、ここからが問題

新しいVPCのパブリックサブネットでAmazon Linux EC2インスタンスを起動します。インスタンスの詳細で、EFSマウントポイントがあるアベイラビリティーゾーンの1つを選択してから、[Add file system ]を選択して、関数に使用しているのと同じEFSを自動的にマウントします。 EC2インスタンスのセキュリティグループには、defaultのセキュリティグループ(EFSをマウントできるようにする)とSSHへのインバウンドアクセスを与える(インスタンスに接続できるようにする)セキュリティグループを選択します。

ほほー、EC2を立ち上げて、EFSをマウントしろと。面白いな

AMIに関してなにも書いてないが、python3.8で試しているようなので

一応Amazon Linux AMI2を使うことにする

 

EC2作成手順の途中で、ファイルシステムにEFSを追加・・・したのだが

これは後々なぜかうまくいかなかったのでやめた。

f:id:Messerarche:20200703180009p:plain

 普通にEC2を起動して、EFSをマウントしたほうがよい。

なんでうまくいかなかったのか原因を探る元気がないので、うまくいく手順を記すことに力を使う

立ち上がったEC2にて

sudo mkdir efslambda

sudo mount -t efs -o tls,accesspoint=[アクセスポイント名] [ファイルシステムID] efslambda 

してマウントする。詳細は過去記事をみてください。 

 

ちなみに今後のインストール作業に割と時間がかかるので

EC2はメモリ4GBぐらいのを選んだ方がいい。無料枠じゃないけど。

 

SSHを使用してインスタンスに接続し、以下の必要な依存関係を含むrequirements.txtファイルを作成します。 

 

 今回必要なパッケージ情報は以下。

vi requirements.txtしてリストアップする。

tensorflow

keras

numpy

pandas 

 

ちなみに、最新を入れずに過去のバージョン

つまりpython3.7だとか、tensorflow1.14.0だとかを入れようとすると

lambdaの実行がまったくうまくいかなくて心折れそうになった。

少しでも容量の節約を・・・と過去バージョンに拘っていたけど

EFSがきた以上、容量気にせず最新で入れてしまった方が良いみたいだ

 

EFSは、EC2によって/mnt/efs/fs1の下に自動的にマウントされます。そこで、/mlディレクトリを作成し、パスの所有者を、現在接続(ec2-user)しているユーザーとグループに変更します。

sudo mkdir /mnt/efs/fs1/ml

sudo chown ec2-user:ec2-user /efslambda

手動でマウントしたのでmkdirは無視して(斜線済)

sudo chown のほうだけ実行

 

まずはpythonのインストール

 

(2022/12/20更新)

以下の通り設定すればスマートにできた。

 

今入っているバージョンを確認

python3 -V
python 3.7.15
 
3.8が使えることを確認
amazon-linux-extras | grep -i python
→ 44 python3.8 available [ =stable ]
 
python3.8のインストールをできるよう許可し、インストール
sudo amazon-linux-extras enable python3.8
sudo yum install python3.8 -y
 
python 3.8がデフォルト設定になるよう設定
echo 'alias python=python3.8' >> ~/.bashrc
source ~/.bashrc
 
3.8に変わったか確認

(2022/12/20更新ここまで)

 

無事、pythonのインストールが終わったら

pip3.8 install -t /efslambda/lib38 -r requirements.txt

で各種パッケージをインストール。

結構時間がかかる

 

無事終わったら

最後に、EFSアクセスポイントに使用したユーザーとグループに/mlパス全体の所有権を与えます。

以下を実行。

sudo chwon -R 1001:1001 /efslambda

 

lambdaの動作環境設定 

次はlambda関数に戻ってきて、環境変数の書き換え

f:id:Messerarche:20200703203625p:plain

 

使用するランタイムに応じて、依存関係が展開パッケージに含まれていないか、レイヤーに含まれていない場合に、依存関係を探す場所を指示する方法を見つける必要があります。 Pythonの場合、PYTHONPATH環境変数/mnt/inference/libに設定しています。

だそうなので、PYTHONPATHをさっきパッケージを詰め込んだ/mnt/slp/lib38に割当

f:id:Messerarche:20200704031132p:plain



ガイドではモデルもEFSに入れているみたいだけど、まずは動くところを確認したいのでこれで。

 

次に基本設定もいじる

f:id:Messerarche:20200704031459p:plain

 

ランタイム=Python3.8

メモリ=1024MB

タイムアウト=3分

 

 実行してみる

いよいよ必要なパッケージを全部importして実行してみる

f:id:Messerarche:20200704031311p:plain

実行!

 

f:id:Messerarche:20200704031604p:plain

やった〜!

ブログだと1本道で成功を歩んでいるが、

実際はいろいろあって半日以上費やし、もう目がしぱしぱ。

 

後日談

EFS配下にモデルとインプットファイルを置いて、機械学習の推論を実行してみた。

ソース全部は載せられないが、一部抜粋でこんな感じ

f:id:Messerarche:20200706175832p:plain

 

結果・・・成功!気持ちいい〜

f:id:Messerarche:20200706175932p:plain

 

 

おわりに

まあでもserverlessだのレイヤーだのzipで固めるだの言う話よりは断然わかりやすいし

コードもコンソール上でいじれて扱いやすいし

設定さえうまくできれば良いことしかない。最高だ。

 

ということでようやく下地が整ったので、

モデルファイル食わせたり、トリガーの設定したりができる。

それはまた今度で。

 

おまけ1

ubuntu使っているので完全には参考にならなかったが

動画でセットアップする手順公開してる人がいたので貼っておく

www.youtube.com

 

 

おまけ2

ちなみに

python3.7以下の環境でtensorflowを動かそうとすると

インストールまではうまくいくが、lambda実行時に以下のようなメッセージが出て壁にぶち当たる。

ImportError: /lib64/libm.so.6: version `GLIBC_2.23' not found (required by /mnt/slp/lib/tensorflow/python/_pywrap_tensorflow_internal.so)


Failed to load the native TensorFlow runtime.

 

amazon web services - [AWS Lambda]: How to fix "version GLIBC 2.27 Not found" - Stack Overflow

無理やりglibを移植すれば動くらしいけど、自分には無理だった

 

せっかく調べた関連コマンドものせとく

$ ls -l | grep libm.so
-rw-r--r-- 1 root root 141 1月 17 06:34 libm.so
lrwxrwxrwx 1 root root 12 5月 27 00:46 libm.so.6 -> libm-2.26.so

 

 strings libm-2.26.so | grep GLIBC
GLIBC_2.2.5
GLIBC_2.4
GLIBC_2.15
GLIBC_2.18
GLIBC_2.23
GLIBC_2.24
GLIBC_2.25
GLIBC_2.26
GLIBC_PRIVATE
GLIBC_2.15 

python3.8で快調に動いたので、こっちはもう追わない。

 

 

 

 

AWSをいじり倒す(13-2.lambda+serverless)

再びこちらの記事に戻ってきて

TensorFlowをAWS Lambdaで使って競艇予想APIを爆誕させました!! - Qiita

 Serverlessを使ったlambda関数の定義。

果たしてこれで容量問題を掻い潜って関数が実行できるのか。

まずはやってみよう

 

 

さっそく、Serverlessを動かすためのnode.jsのインストール

MacにNode.jsをインストール - Qiita

 

2箇所エラーがでて詰まった

・1個目

Homebrewで「Warning: The following directories are not writable by your user:」とエラーが出た際の解消法 - Qiita

エラーメッセージに解決方法も書いてあるので、whoamiのところだけ書き換えて

コピペで解消

・2個目

Homebrewで「Warning: The following directories are not writable by your user:」とエラーが出た際の解消法 - Qiita

ディレクトリを作って解消。

 

どちらも記事の記載を淡々とこなせば解消したので特筆することはなし。

 

serverlessをインストール

npm install -g serverless

f:id:Messerarche:20200701174738p:plain

 成功!

 

serverlessからAWSにアクセスするためのIAMアカウントを作成

f:id:Messerarche:20200701175229p:plain

プログラムによるアクセスを選択してユーザを作成。 

 

f:id:Messerarche:20200701175422p:plain

AdministratorAccessを付与。

・・・なんか与えすぎな気もするが一旦作成。

lambdaにデプロイするときにcroud formation使ったりS3バケット作ったりと

いろんなことしまくるようなので、まとめてエイヤ権限ってことなんだろう。

 

ローカル端末から以下のコマンドを実行してserverlessからawsにアクセスできるようにする

serverless config credentials --provider aws --key [アクセスキーID] --secret [シークレットアクセスキー]

 

以下の文が返ってきたら成功 

Serverless: Setting up AWS...

 

プロジェクトを作る。pythonを使うのでtemplateはaws-pythonを選択

serverless create --template aws-python

f:id:Messerarche:20200701180857p:plain

 

コマンドを実行したディレクトリに

handler.py

serverless.yml

の2ファイルができた。

pyにはメインとなるlambda関数を、ymlにはデプロイするサービスの設定を書き込む。

 

lambda-layerとlambda-functionフォルダを作って、handler.pyとserverless.ymlをそれぞれのフォルダにコピーする

mkdir lambda-layer

mkdir lambda-function

cp handler.py lambda-layer/

cp serverless.yml lambda-layer/

cp handler.py lambda-function/

cp serverless.yml lambda-function/

 

lambda-functionの設定

vi lamda-function/serverless.yml

serverless-python-requirementsという機能を使って

追加のパッケージをインストールしてくれるらしいので、その設定を書く。

なお、serverless-python-requirementsを利用する場合、Dockerも必要となる

今回はインストール済みだったので手順は割愛

 

とはいえserverless.ymlのお作法はよくわからないので参考サイト通りに作成。

ただ、結局こっちでもslimPatternsにいらなさそうなファイルをずらずらと列挙して容量削減を図ることになる

service: [サービス名]

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    dockerizePip: true
    zip: true
    slim: true
    slimPatterns:
      - "**/debug"
      - "**/grpc"
      - "**/h5py"
      - "**/markdown"
      - "**/pkg_resources"
      - "**/setuptools"
      - "**/tensorboard/plugins"
      - "**/tensorboard/webfiles.zip"
      - "**/tensorflow_core/contrib"
      - "**/tensorflow_core/examples"
      - "**/tensorflow_core/include"
      - "**/tensorflow_estimator"
      - "**/werkzeug"
      - "**/wheel"
  requirementsService: [サービス名]-layer
  requirementsExport: [サービス名]Layer
  requirementsLayer: ${cf:${self:custom.requirementsService}-${self:provider.stage}.${self:custom.requirementsExport}}

provider:
  name: aws
  runtime: python3.7
  stage: dev
  region: ap-northeast-1
  iamRoleStatements:
      - Effect: "Allow"
        Action:
          - s3:ListBucket
          - s3:GetObject
        Resource:
          - "arn:aws:s3::*"

functions:
  classify:
    handler: handler.classify
    memorySize: 2048
    timeout: 60
    layers:
      - ${self:custom.requirementsLayer}

斜体太字部分は自分の環境に応じて書き換え。

 

handler.pyには実行したいプログラムを記載。

とりあえず動作確認をしたいので先頭に

try:
    import unzip_requirements
except ImportError:
   pass

import json
import pandas
from keras.models import load_model
import numpy

をつけるだけつけた。

最初の4行はserverlessで必須、後半の4行で動作確認

 

requirements.txtを新たに作る

vi requirements.txt

そこに追加で入れたいパッケージを指定。

tensorflow == 1.14.0

 

 

lambda-layerの設定

service: [サービス名]-layer

plugins:
  - serverless-python-requirements

provider:
  name: aws
  region: ap-northeast-1
  runtime: python3.7
  stage: dev

custom:
  pythonRequirements:
    dockerizePip: true
    layer:
      compatibleRuntimes:
        - python3.7
      slim: true
      strip: false

resources:
  Outputs:
    [サービス名]Layer:
      Value:
        Ref: PythonRequirementsLambdaLayer

インデントとか空白とかその通りにしないとエラーが出るので注意

(これで数時間無駄にした)

 

こちらにも

requirements.txtを新たに作る

vi requirements.txt

そこに追加で入れたいパッケージを指定。

keras == 2.3.1

numpy == 1.18.1

pandas == 1.0.1

 

設定が終わったら以下のコマンドを実行してデプロイ。

cd lambda-layer

serverless deploy

cd ../lambda-function

serverless deploy

 

・・・

  An error occurred: HandlerLambdaFunction - Unzipped size must be smaller than 64198656 bytes (Service: AWSLambdaInternal; Status Code: 400; Error Code: InvalidParameterValueException; Request ID: db8cece7-fd05-4e73-9066-d0921795792c).

というエラーが出て停止。

結局サイズが足りずにだめなんかーい

 

また容量との格闘か・・・

とりあえず組み合わせを変えてみる

具体的にはLayerのrequiementsを

numpy == 1.18.1

 

functionのrequirementsを

tensorflow == 1.14.0
keras == 2.3.1
pandas == 1.0.1

というふうに組み合わせを変えてみる

重複するパッケージがうまいこと作用することを祈って

 

Serverless: Checking Stack update progress...
...............
Serverless: Stack update finished...

 

う、うまくいった・・・。本当にギリギリの戦いだ

AWS lambdaにデプロイした関数ができているので、早速テスト

 

"errorMessage": "[Errno 28] No space left on device",
"errorType": "OSError",

 

はい、エラー

Lambda実行時に 'No space left on device'とエラーが出るときの対処法 - Qiita

 

 tmpを使い切った模様。

果たして、この関数1つで使い切ったのか、トライアンドエラーしているうちに使い切ったのか・・・

とりあえずtmpをrmするコマンドを放り込む。

f:id:Messerarche:20200703162759p:plain

こ、コンソール上でプログラムの変更ができない・・・。

つまり毎回デプロイせなあかんのか!?

デプロイ1回に5分ぐらいかかるのでこれは地味にめんどくさい。

 

しょうがないので再デプロイ

→No space left on device

 

エラーメッセージを読むに、zip解凍した時点でtmp使い切っている疑惑。

これもうだめかもわからんね

 

頭抱えてたらまた新たな突破口を発見

新機能 – Lambda関数の共有ファイルシステム – Amazon Elastic File System for AWS Lambda | Amazon Web Services ブログ

 

いくつかのユースケースではLambda関数によって実装がより容易になります。例えば:

  • /tmpで使用可能な容量(512MB)より大きいデータを処理またはロードする。

ど、ドンピシャ〜

しかも2020/6/17の記事て。最近すぎワロタ 

 

械学習の推論を実装するLambda関数を作成するには、コード内で、必要なライブラリをインポートして機械学習モデルをロードできる必要があります。たいてい、そのようなことをすると、それらの依存関係の全体的なサイズは、デプロイメントパッケージサイズの現在のAWS Lambdaの制限を超えます。これを解決する1つの方法は、ライブラリを適切に最小化して関数コードとともにデプロイし、モデルをS3バケットからメモリ(モデルの処理に必要なメモリを含めて最大3 GB)または/tmp(最大512 MB)に配置します。このようなモデルのダウンロードとカスタムが必要な最小化は、実装するのが簡単ではありませんでした。しかし、これからはEFSを使用できます。

数日間悩んできたことが全部書いてあるしマジかよ。

もうlambdaやめようと思ってたけど、もうちょっと頑張る。

長くなってきたので次回へ

 

 

 

AWSをいじり倒す(13.lambda)

食わず嫌いしてたlambdaを使ってみる。

 

今回は目的があって、

s3にファイルが入ったタイミングで

tensorflowで作成したモデルを使って推論をするプログラムを動かしたい、というもの。

 

まずは下調べしてみたけど、やっぱりちょっとめんどくさい。

今回は手元にあるpythonのコードを動かすのが目的なんだけど、

import numpyとかpandasとかしてたら、

事前にそれらパッケージをzipでアップロードしてあげないといけないようだ

pandasをLambdaのLayerとして追加する - Qiita

そのパッケージも、ローカルで作ってもだめで、指定したEC2で作る必要がある

 

 

まず最初にややこしいのが、使うpythonのバージョンによって

アップロードするパッケージの生成元となるEC2のAMIを選ばないといけない点

AWS Lambda ランタイム - AWS Lambda

今回使おうとしているpython3.7のOSはAmazon Linux(2ではない)

のでこのAMIを選んでインスタンスを起動。

 

次に、EC2のデフォルトpythonのバージョンが2.7だかなんだかで古いので

目的のバージョンのpythonをインストール。

現時点でyumに対応したpythonは3.6系までしかないので、

3.7以降はソースからインストールするしかない

 

今回はPython3.7.6を使う。

具体的な手順はここが参考になった

AWS Lambdaでpandasを利用する(Lambda Layers編) - marmarossa’s blog

 

まず目的のpythonをダウンロード

curl https://www.python.org/ftp/python/3.7.6/Python-3.7.6.tgz -o Python-3.7.6.tgz

 

解凍

tar xzf Python-3.7.6.tgz

 

ディレクトリに移動

cd Python-3.7.6

 

必須ライブラリをインストール

yum install gcc openssl-devel bzip2-devel libffi-devel

 

コンパイルしてインストール

./configure --enable-optimizations

sudo make altinstall

 

パッケージをインストールするディレクトリを作成

mkdir python/lib/python3.7/site-packages/

 

pandasのインストール

pip3.7 install pandas -t ./python/lib/python3.7/site-packages/

kerasのインストール

pip3.7 install keras -t ./python/lib/python3.7/site-packages/

 

zipに固める

zip pandaskeras.zip python/ -r

 

S3にアップロード(EC2へのS3アクセス権限付与など忘れずに)

aws s3 mv pandaskeras.zip s3://[s3bucket名]/module/pandaskeras.zip

 

S3のコンソール画面からアップロードされたことを確認して、

オブジェクトURLをメモっておく

f:id:Messerarche:20200701192746p:plain

 

 

 

 

 

lambdaの画面に移動

Additional Resourcesからレイヤーを選択、レイヤーの作成

f:id:Messerarche:20200701035239p:plain


レイヤーの作成。

f:id:Messerarche:20200701192926p:plain

S3からアップロードを選択し

リンクURLに先ほどメモしたURLを貼る
互換性のあるランタイムオプションはPython3.7を選択

 

 

次に関数の作成

f:id:Messerarche:20200701161804p:plain

 

まずはHello Worldから。

f:id:Messerarche:20200701162014p:plain


ロールは特に用意していないので作ってもらう

f:id:Messerarche:20200701162114p:plain

関数の作成を実行。

 

ほどなくして作成が完了

f:id:Messerarche:20200701162402p:plain

 

f:id:Messerarche:20200701162421p:plain

何かいろいろできそう(小並感)

 

ともあれ関数を実行してみる。

右上のテストボタンを押す

f:id:Messerarche:20200701163234p:plain

こんな画面が出てくるので、イベント名に適当にhelloと入力、作成

 

f:id:Messerarche:20200701163328p:plain

再度テストを実行

 

f:id:Messerarche:20200701163355p:plain

結果がずらずらとでてきた。とりあえずは動いたようだ。

 

 

さて、問題のimport pandasができるのかを試す

レイヤーを選択

f:id:Messerarche:20200701163526p:plain



お客様のレイヤーにさっき登録したレイヤーが出てきた。

f:id:Messerarche:20200701193452p:plain
numpyとscipyならデフォルトで用意してくれとるのね。

 

ちなみに、余談だが試行錯誤してるときはこんな怒られ方をした

f:id:Messerarche:20200701163800p:plain

どうも、追加パッケージは合計262MBまでしかだめらしい。

AWS LambdaでTensorFlow 2.0を使った画像分類 - ユニファ開発者ブログ

この辺にいかにやりくりするか、みたいなテクニックが書いてあった。

ええ、そこって気を使わないかんとこなの・・・。

いろんなものimportして大きめのプログラム動かそうと思ったらlambdaは諦めて

EC2で普通に動かすしかないのかも。

 

 

とりあえず追加は完了。

f:id:Messerarche:20200701193743p:plain



 

プログラムにimport pandasとかを追加してテストしてみる。

f:id:Messerarche:20200701193836p:plain

ソースを変更したら、Saveしたのちにテストを実行


f:id:Messerarche:20200701193924p:plain


どうやらうまくいったらしい。

import kerasも試してみる

 

f:id:Messerarche:20200701194540p:plain

 

ぐえ、tensorflowもいりますよ、と(凡ミス)。

しかしtensorflowは馬鹿デカ容量なので

上に書いたように262MBなんて余裕で超えて、そのままlambdaには持ってこれない。
 

 

一応EC2にインストールしてみる

pip3.7 install tensorflow -t ./python/lib/python3.7/site-packages/ --no-cache-dir

(--no-cache-dirについては以下)

AWS EC2でMemoryErrorとNo space left on deviceで詰まった話 - Qiita

 

ずらずら、とインストールされた中からダメ元で重そうなのを消してみる。

f:id:Messerarche:20200701195221p:plain

tensorboardとかは確かにいらんよなぁ

 

容量を確認するコマンドを打ってみる

du -sh ./*/

f:id:Messerarche:20200701195749p:plain

本体が1.5GB〜!!

 

犯人探ししていこう

f:id:Messerarche:20200701195917p:plain

 

これか。

f:id:Messerarche:20200701200308p:plain

 

これ消しちゃまずそうなやつ・・・え、みんなどうやってんの?

Running TensorFlow on AWS Lambda using Serverless - Mike Moritz - Medium

slim — Removes unneeded files and directories such as *.so*.pycdist-info, etc.

 

まじ?これ消していいの?(わかってない

どうみてもプログラムの本体だから無理だよなぁ

 

物は試しってことで除外してzipしてみる

[ec2-user@ip-172-31-14-167 ~]$ zip pkt.zip python/ -r -x python/lib/python3.7/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so

 

できたのは164MBのzip

f:id:Messerarche:20200701202612p:plain

 

レイヤーに登録しようとすると

f:id:Messerarche:20200701202728p:plain

これでもでかすぎてだめか。

 

 tensorflowさえ使わなければ万事うまくいきそうなんだけど、

使うんだよな〜〜

 

次回、どうも成功してる事例っぽいserverlessからデプロイするやり方をダメ元で試してみる・・・。 

 

 

あと、s3に格納されたファイルを引数としたり、

結果をs3に格納したりする方法を探してみると

【AWS Lambdaの基本コードその2】 S3へのファイル保存 | ナレコムAWSレシピ

これをみると普通にpythonのコードでs3にアクセスする感じなのね。

てっきり、lambdaの機能として提供されているのかと思った。

 

 そうなってくると、あとはimportがうまくいけば

この関数のトリガーイベントの設定と、

成功時・失敗時ログをどこに流すかって設定するぐらいか。

 

なんだかlambdaの全体像は見えてきたが、超えるべき山がまだ高い

AWSをいじり倒す(番外編.LightSailのredmineにgit設定)

LightSailで建てたRedmineの中にGitを入れ込むという設定。

別にLightSailがEC2(redmineの入ったAMI利用)であってもよい。設定は一緒。

これらの特徴はbitnamiのRedmineを利用するという点にある。

bitnami式Redmine色々制約があるので、大半のネット記事を鵜呑みにするとうまくいかず、死ぬほど紆余曲折したのでメモしておく。

最後は妥協しているので参考程度に。

bitmani式ではなくて自前で構築する場合は、この記事は逆に参考にならんので注意

 

環境は

Ubuntu 16.04.6

Bitnami Redmine 4.1.0-8 

 

 

gitのインストール

bitnami式redmineにgitは最初から入っているので不要。

f:id:Messerarche:20200524170851p:plain

 

リポジトリ を準備。

作業ディレクトリは適当に以下とした。

/home/bitnami/git/repos

 

リポジトリ を作成

mkdir test.git
cd test.git
git init --bare
git update-server-info

なお、pushするたびにupdate-server-infoしないといけないらしいので

それを回避する設定もいれておく

mv hooks/post-update.sample hooks/post-update

実行権限も付与

cd ../
chmod -R o+w test.git

 

 

 

 

bitnamiで建てたRedmineの、Apacheやwww-なんとかみたいなユーザ相当はdaemon。

chown -R bitnami:daemon .

それはこのサイトをみて知った。

Amazon Lightsailの諸々ファイル置場 | It works for me

 他にもログの場所など有益な情報がまとまっていてありがたい

 

gitでやり取りする時に、必ずユーザとパスワードを要求したいので、その準備。

htpasswd -c -b /home/bitnami/git/.htpasswd ユーザ名 パスワード

 -bをつけて1ラインで登録してしまわないとバグるみたいな記載をみたので念の為書いておく

 

Apache用コンフィグの作成

bitnami式redmineの裏で動いているのはApache

主にこのディレクトリ 配下にいろんなconfigが入っている

/opt/bitnami/apache2/conf/

この配下で作業を行っていく

 

git.confを新たに作成。

まずは最小構成の内容をgit.confに記載する。

Alias /git /home/bitnami/git/repos

<Location /git>
    AuthType basic
    AuthName "Git"
    AuthUserFile /home/bitnami/git/.htpasswd
    Require valid-user
</Location>

 

 

このgit.confを読み込んでもらうために、同一ディレクトリにあるhttpd.confの最終行に

Include "/opt/bitnami/apache2/conf/git.conf"

を追加

 

これで最小構成が完成。システムのリスタートを行う。

bitnami式redmineのリスタートコマンドは以下。

sudo sh /opt/bitnami/ctlscript.sh restart

 

いったんここまででテスト。

ローカル環境にて、cloneを試す。

git clone https://example.com/git/test.git

ユーザ名入力

パスワード入力

Cloning into 'test'...

warning: You appear to have cloned an empty repository.

この返答がくればOK

 

ちなみに、まだcloneはできてもpushはできない。

touch testfile
git add testfile
git commit -m "first commit" 

 [master 452f009] first commit

1 file changed, 0 insertions(+), 0 deletions(-)

create mode 100644 testfile

git remote set-url origin https://example.com/git/test.git 
git push -u origin master

 error: Cannot access URL https://example.com/git/test.git/, return code 22

fatal: git-http-push failed

error: failed to push some refs to 'https://example.com/git/test.git'

 

pushするための設定

pushを行うためには、git.confに追加の設定が必要でそのやり方は2通りある

1.git-http-backendを使う

2.WebDavを使う

のどちらかを行う必要がある。

なお、SSHは使わずにhttpでなんとかしたい、というのが前提。

(鍵管理したくないので) 

 

さて、git-http-backendを有効化する手順は巷にたくさん書いてあるが、

それをそのまま入れると失敗するのが最大のハマりポイント。

 

具体的にはこういうやつだ

SetENV GIT_PROJECT_ROOT /home/bitnami/git/repos
SetENV GIT_HTTP_EXPORT_ALL
ScriptAlias /git "/usr/lib/git-core/git-http-backend"

Alias /git /home/bitnami/git/repos

<Location /git>
    AuthType basic
    AuthName "Git"
    AuthUserFile /home/bitnami/git/.htpasswd
    Require valid-user
</Location>

上3行を追加し、取り消し線のところを削除

他にもなんだか装飾されたコンフィグが公開されているが、最低限これだけでいい。

 

リスタートしてconfファイルを読み込む

sudo sh /opt/bitnami/ctlscript.sh restart

 

すると、ローカルで実行した結果がこうなる

git push -u origin master

 fatal: repository 'https://example.com/git/test.git/' not found

ちなみに、cloneも失敗するようになる。

これが何故なのかわからず死ぬほど時間を使った。

 

 

結論はbitnami式redmine環境特有のものだった。

自分なりに調べた結果を言葉にすると

bitnami式は動作するための最小限のモジュールしかインストールしていない

かつ、通常インストールされる場所とは別のディレクトリにモジュールを置いているのでapt-get等で追加インストールしてもそれだけでは反映されない。

 

なので、普通に動かないし、追加でpkgインストールしても動かないという二重苦。

ちなみに2番目のDAVの設定にも手を出してみたがこれまた沼が深そうで諦めた。

また、手探りでa2enmod cgiコマンド打ったあと再起動したらredmineが立ち上がらなくなり、ubuntuのデフォルトページが表示されるようになってしまった。クソ焦って慌てて復旧させたが、configのいかんところが書き変わってしまったんだろうか・・・。若干トラウマ

 

 

さて、試行錯誤を経てたどり着いたのがこのブログ

bitnami redmine と同じホストで git 連携する - criticablog

完全ではないが、動くところまではもっていくことができた。

以下にコピペな部分もあるが手順を書いておく。

 

fcgiwrapインストール

sudo apt-get install fcgiwrap

  ユーザグループ設定

vi /etc/default/fcgiwrap
FCGI_USER=daemon
FCGI_GROUP=bitnami
FCGI_SOCKET_OWNER=daemon
FCGI_SOCKET_GROUP=daemon

  

 

git.confを大改造。Subversionとかは使わないので自分用に多少アレンジ

<IfModule !perl_module>
LoadModule perl_module modules/mod_perl.so
</IfModule>
<IfModule !env_module>
LoadModule env_module modules/mod_env.so
</IfModule>
PerlLoadModule Apache::Authn::Redmine

# Gitの設定
<IfModule !proxy_module>
LoadModule proxy_module modules/mod_proxy.so
</IfModule>
<IfModule !proxy_fcgi_module>
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
</IfModule>

LogLevel trace2

AliasMatch "/git/(.+)" "/opt/bitnami/git/libexec/git-core/git-http-backend/$1"
<Location /git>
    SetHandler "proxy:balancer://fcgi59618workaround"

    SetEnv GIT_HTTP_EXPORT_ALL ""
    SetEnv GIT_PROJECT_ROOT "/home/bitnami/git/repos"

    AuthType Basic
    AuthName "Git"
AuthUserFile /home/bitnami/git/.htpasswd Require valid-user </Location> <Proxy "balancer://fcgi59618workaround"> BalancerMember "unix:/run/fcgiwrap.socket|fcgi://." </Proxy>

 

モジュールの配置

mkdir -p /opt/bitnami/perl/lib/site_perl/5.16.3/Apache/Authn
ln -sv /opt/bitnami/apps/redmine/htdocs/extra/svn/Redmine.pm /opt/bitnami/perl/lib/site_perl/5.16.3/Apache/Authn/Redmine.pm

そして

sudo sh /opt/bitnami/ctlscript.sh restart

すると、無事clone/pushどちらも動いた。

先人の知恵さまさま・・・!

 

これで、http通信+password要求でgitが使える環境が完成。

https設定ができていればhttpsでも問題なく動いた。

 

redmineとgitの連携は

RedmineでGitを連携させる - Qiita

あたりをみながら設定してなんなく動いた。

 

 

 

ここからはうまくいっていなのでただの参考記事

どうもredmineで作ったID/PASSをgitの認証に使うことができるとのこと

これは便利と思って試そうとして死んだ。

 

先人様のサイトにあるこのconfigをgit.confに追加でぶちこむ。

    # Redmine プロジェクトのメンバー管理と連動したアクセス制御。
    PerlAccessHandler Apache::Authn::Redmine::access_handler
    PerlAuthenHandler Apache::Authn::Redmine::authen_handler
    # /opt/bitnami/apps/redmine/htdocs/config/database.yml と同じ値にする。
    RedmineDSN "DBI:mysql:database=bitnami_redmine;host=localhost;mysql_socket=/opt/bitnami/mysql/tmp/mysql.sock"
    RedmineDbUser "bitnami"
    RedmineDbPass "****"

なお、DbPassは

/opt/bitnami/apps/redmine/htdocs/config/database.yml

に書いてある。

 

これでclone実行するとこんな感じのエラー

git clone https://example.com/git/test.git

Cloning into 'test'...

fatal: unable to access 'https://example.com/git/test.git/': The requested URL returned error: 500

 

 

/opt/bitnami/apache2/logs/error_logをみるとこんなことが書いてある

[Mon May 25 14:36:04.034645 2020] [perl:error] [pid 21399:tid 139890079483648] [client 118.2.90.3:54909] install_driver(mysql) failed: Can't locate DBD/mysql.pm in @INC (@INC contains: /opt/bitnami/git/lib/site_perl/5.16.3 /opt/bitnami/perl/lib/5.16.3/x86_64-linux-thread-multi /opt/bitnami/perl/lib/5.16.3 /opt/bitnami/perl/lib/site_perl/5.16.3/x86_64-linux-thread-multi /opt/bitnami/perl/lib/site_perl/5.16.3 /opt/bitnami/perl/lib/5.16.3/x86_64-linux-thread-multi /opt/bitnami/perl/lib/site_perl/5.16.3/x86_64-linux-thread-multi /bitnami/ruby25stack-linux-x64/output/perl/lib/site_perl/5.16.3/x86_64-linux-thread-multi /bitnami/ruby25stack-linux-x64/output/perl/lib/site_perl/5.16.3 /bitnami/ruby25stack-linux-x64/output/perl/lib/5.16.3/x86_64-linux-thread-multi /bitnami/ruby25stack-linux-x64/output/perl/lib/5.16.3 . /opt/bitnami/apache2) at (eval 6) line 3.\nPerhaps the DBD::mysql perl module hasn't been fully installed,\nor perhaps the capitalisation of 'mysql' isn't right.\nAvailable drivers: DBM, ExampleP, File, Gofer, Proxy, Sponge.\n at /opt/bitnami/perl/lib/site_perl/5.16.3/Apache/Authn/Redmine.pm line 557.\n

太字のところに着目

DBD/mysql.pmが見つからねーよ、と。

これが先に説明したbitnami特有のモジュール足りてないアレ。

 

これについて戦っている記事を2つ見つけた。

git - install_driver(mysql) failed: Can't locate DBD/mysql.pm - Stack Overflow

なんでこのエラーがでるかの仕組みがしっかりと書いてあるが、

悲しいかなこのサイトの結論は

「サポートに繰り返し言ってくしかないね!」

 

こっちは気合で解決しましたと書いてある

Subversion login - install_driver(mysql) failed: Can't locate DBD/mysql.pm in @INC - Redmine - Bitnami Community

過去サーバから設定ファイルをコピーしてきて、config書き換えたらうまくいったんだと。

試しにファイルをいくつかそれっぽいコピーしてみたけど、

そこまで知識がないので泥沼化して諦めた。つら。

 

どうも昔のredmineにはちゃんと入ってたモジュールっぽいんだよなぁ。

現時点の最新(4.1.0-8)には含まれてないのは謎