AWS CDKで、cdk deployしたらエラー「NoSuchBucket: The specified bucket does not exist」

先日、S3に不要なバケットがたまっていたため、バケットを全削除をするスクリプトを作成・実行しました。

その後、AWS CDK + Pythonで、 cdk deploy したところ、

 ❌  <your_backet_name> failed: NoSuchBucket: The specified bucket does not exist
The specified bucket does not exist

とエラーになり、AWS CDKを使ったデプロイができなくなりました。

 
この時の対応でしばらく悩んだため、メモを残します。

 

目次

 

環境

 

調査と対応

前述の通りバケットを全削除してしまったため、必要なバケット <your_backet_name> が無いせいかと考えました。

そこで、手動でエラーが出ている名称のバケットを作成したものの、エラーは変わりませんでした。

これにより、他にも消してはマズいバケットを消してしまったのかなと考えました。

 

CloudFormationのスタックを見たところ、 CDKToolkit という気になるスタックがありました。

スタックの説明には

The CDK Toolkit Stack. It was created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.

とありました。

Getting Startedページに書かれていないため忘れていましたが、AWS CDKでデプロイする前に、 cdk bootstrap をやっていたことを思い出しました。
Getting Started With the AWS CDK - AWS Cloud Development Kit (AWS CDK)

 

そこで、改めて cdk bootstrap したところ、エラーが変わりました。

$ cdk bootstrap
 ⏳  Bootstrapping environment aws://<account>/<region>...
CDKToolkit: creating CloudFormation changeset...
 0/2 | 8:22:48 PM | UPDATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket 
0/2 Currently in progress: StagingBucket
 1/2 | 8:24:34 PM | UPDATE_FAILED        | AWS::S3::Bucket | StagingBucket The specified bucket does not exist (Service: Amazon S3; Status Code: 404; Error Code: NoSuchBucket; Request ID: xxx; S3 Extended Request ID: xxx)
 1/2 | 8:24:34 PM | UPDATE_ROLLBACK_IN_P | AWS::CloudFormation::Stack | CDKToolkit The following resource(s) failed to update: [StagingBucket]. 
 ❌  Environment aws://<account>>/<region> failed bootstrapping: Error: The stack named CDKToolkit is in a failed state: UPDATE_ROLLBACK_COMPLETE
The stack named CDKToolkit is in a failed state: UPDATE_ROLLBACK_COMPLETE

 

ただ、新規で cdk bootstrap を行ったつもりが、ログには UPDATE_IN_PROGRESS と出ていました。

そのため、もしかしたら、スタック CDKToolkit が存在しているせいかもしれないと考えました。

スタック CDKToolkit について、AWSドキュメントの記載は以下です。

Bootstrapping your AWS Environment
Before you can use the AWS CDK you must bootstrap your AWS environment to create the infrastructure that the AWS CDK CLI needs to deploy your AWS CDK app. Currently the bootstrap command creates only an Amazon S3 bucket.

You incur any charges for what the AWS CDK stores in the bucket. Because the AWS CDK does not remove any objects from the bucket, the bucket can accumulate objects as you use the AWS CDK. You can get rid of the bucket by deleting the CDKToolkit stack from your account.

https://docs.aws.amazon.com/cdk/latest/guide/tools.html

 

そこで、スタック CDKToolkit を削除した後、 cdk bootstrap を行ったところ、成功しました。

$ cdk bootstrap
 ⏳  Bootstrapping environment aws://<account>/<region>...
CDKToolkit: creating CloudFormation changeset...
 0/2 | 8:31:37 PM | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket 
 0/2 | 8:31:39 PM | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket Resource creation Initiated
 1/2 | 8:32:01 PM | CREATE_COMPLETE      | AWS::S3::Bucket | StagingBucket 
 2/2 | 8:32:03 PM | CREATE_COMPLETE      | AWS::CloudFormation::Stack | CDKToolkit 
 ✅  Environment aws://<account>/<region> bootstrapped.

 

次に、 cdk deploy してみたところ、デプロイが成功しました。

$ cdk deploy
<your_stack>: deploying...
<your_stack>: creating CloudFormation changeset...
 0/3 | 8:33:12 PM | CREATE_IN_PROGRESS   | AWS::IAM::User     | xxx (xxx) 
 0/3 | 8:33:12 PM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata 
 0/3 | 8:33:13 PM | CREATE_IN_PROGRESS   | AWS::IAM::User     | xxx (xxx) Resource creation Initiated
 0/3 | 8:33:14 PM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata Resource creation Initiated
 1/3 | 8:33:14 PM | CREATE_COMPLETE      | AWS::CDK::Metadata | CDKMetadata 

 ✅  <your_stack>

 

これより、AWS CDKを使っている場合は、S3のバケット CDKToolkit を削除してはいけないことが分かりました。

DjangoのListViewで、対象データがない場合は404ページを表示する

DjangoでListViewを使う場合、デフォルトではモデルにデータがない時は HTTP200 で、データがない状態で表示されます。

f:id:thinkAmi:20191029062825p:plain:w400

 
ただ、モデルにデータがない場合に HTTP404 を表示したい時はどうするか、調べたことをメモしておきます。

 

目次

 

環境

 

調査

公式ドキュメントにより、ListViewが継承している MultipleObjectMixin が持つ allow_empty を使えば良さそうでした。

 
stackoverflowにも、同じような回答がありました。
Django: get_object_or_404 for ListView - Stack Overflow  
 
そこで、以下のような

  • urls.py
  • models.py
  • todo_list.html

を用意し、 allow_empty の挙動を試してみます。

urls.py

urlpatterns = [
    path('', NotFoundListView.as_view(), name='todo_list'),
]

models.py

from django.db import models


class Todo(models.Model):
    title = models.CharField(max_length=100)
    content = models.CharField(max_length=255)

todo_list.html

<body>
<main class="container">
    <table>
        <thead>
        <tr>
            <th>ID</th>
            <th>タイトル</th>
            <th>内容</th>
        </tr>
        </thead>
        <tbody>
        {% for todo in todo_list %}
            <tr>
                <td>{{ todo.id }}</td>
                <td>{{ todo.title }}</td>
                <td>{{ todo.content }}</td>
        </tbody>
    </table>
    {% endfor %}
</main>
</body>

 

allow_empty=Trueの場合

使うListViewはこんな感じです。

from django.views.generic import ListView
from listview404.models import Todo


class NotFoundListView(ListView):
    allow_empty = True  # デフォルトのまま
    model = Todo

 

結果はこちら。HTTP200です。

f:id:thinkAmi:20191029062825p:plain:w400

 

allow_empty=Falseの場合

allow_empty のみ変更します。

from django.views.generic import ListView
from listview404.models import Todo


class NotFoundListView(ListView):
    allow_empty = False  # 変更
    model = Todo

 

HTTP404 が表示されました。

f:id:thinkAmi:20191029062859p:plain:w400

 

ソースコード

Githubに上げました。 listview404 が今回のアプリです。
https://github.com/thinkAmi-sandbox/Django22_generic_view-sample

AWS CDKで、cdk deployしたら「Unable to resolve AWS account to use」エラー

AWS CDKを使って cdk deploy したところ

$ cdk deploy
Unable to resolve AWS account to use. It must be either configured when you define your CDK or through the environment

というエラーが発生したため、対応した時のメモ。

 

環境

 

原因と対応

CDK CLIdoctor コマンドを使って確認したところ

$ cdk doctor
ℹ️ CDK Version: 1.9.0 (build 30f158a)
ℹ️ AWS environment variables:
  - AWS_SECRET_ACCESS_KEY = <redacted>
  - AWS_REGION = us-east-1
  - AWS_ACCESS_KEY_ID = AK<redacted>
  - AWS_S3_GATEWAY = http://http://s3.amazonaws.com
ℹ️ No CDK environment variables

と、自分のAWS CLIのconfigとは異なる値(AWS_REGION)や、設定していない値(AWS_S3_GATEWAY)が出てきました。

 
何か環境変数を指定したかなと思い確認したところ、

$ export -p
...
declare -x AWS_ACCESS_KEY_ID="AK***"
declare -x AWS_REGION="us-east-1"
declare -x AWS_S3_GATEWAY="http://http://s3.amazonaws.com"
declare -x AWS_SECRET_ACCESS_KEY="***"

と、別のところで使った時の環境変数を消し忘れているようでした。

 
そのため、 cdk doctor に出てきた環境変数を消しました。

$ unset AWS_S3_GATEWAY
$ unset AWS_REGION
$ unset AWS_SECRET_ACCESS_KEY
$ unset AWS_ACCESS_KEY_ID

 
再度実行したところ、問題なくデプロイできました。

$ cdk deploy
step-functions: deploying...
step-functions: creating CloudFormation changeset...
 0/2 | 8:12:22 PM | UPDATE_IN_PROGRESS   | AWS::CDK::Metadata               | CDKMetadata 

 ✅  step-functions

AWS CDK + Pythonで、ネストした AWS StepFunctions のワークフローを作ってみた

今年の7月にAWS CDK (Cloud Development Kit) がGAとなりました。
AWS クラウド開発キット (CDK) – TypeScript と Python 用がご利用可能に | Amazon Web Services ブログ

APIリファレンスも公開されているため、これでPythonを使ってAWSのリソースを作成することができるようになりました。
API Reference · AWS CDK

 
ただ、ドキュメントではTypeScriptの書き方がメインであり、Pythonでどう書くのかイマイチ分かりませんでした。

そこで、AWS CDK + Pythonで、ネストした AWS StepFunctions のワークフローを作ってみることにしました。

 
なお、記事の末尾にもあるように、ソースコードは公開しています。この記事はそのソースコードの解説です。

 

目次

 

環境

 
今回作成するStepFunctionsのワークフローです。

メインのStateMachineを実行します。サブのStateMachineを3パラレルで起動します。

f:id:thinkAmi:20190929163040p:plain:w300

 
サブの方は、各タスクにエラーハンドリングが付いています。

f:id:thinkAmi:20190929163143p:plain:w250

 
各StateMachineの機能です。

  • メイン
    • LambdaからS3へファイルを保存
    • サブのStateMachineを3パラレルで起動
  • サブ
    • Lambdaを実行

 

環境構築

Getting Startedに従い、環境を構築します。
Getting Started With the AWS CDK - AWS Cloud Development Kit (AWS CDK)

AWS CDKのCLIをインストールします。

$ npm install -g aws-cdk

$ cdk --version
1.9.0 (build 30f158a)

$ mkdir step_functions

$ cd step_functions/

 

cdk initPythonを使ったCDK環境を作成します。

$ cdk init --language python
Applying project template app for python
Executing Creating virtualenv...


# ちなみに、ディレクトリの中に何かファイルがあるとエラー
$ cdk init --language python
`cdk init` cannot be run in a non-empty directory!

 
Pythonの仮想環境が準備されるため、activate後にインストールします。

$ source .env/bin/activate

$ pip install -r requirements.txt

 
次にAWS CDKで使うリソースに対するモジュールを追加でインストールします。

どのモジュールが必要なのかは、APIリファレンスのトップに記載されています。*1

 
まずは、StepFunctionまわりで必要なモジュールをインストールします。

$ pip install aws_cdk.aws_stepfunctions

$ pip install aws_cdk.aws_stepfunctions_tasks

 
次にLambdaまわり。

$ pip install aws_cdk.aws_lambda

 
S3バケットにファイルを保存するため、S3のモジュールも必要です。

$ pip install aws_cdk.aws_s3

 
あとは、S3バケットにファイルを保存するために、IAMロールも必要になります。

$ pip install aws_cdk.aws_iam

 

作成順について

AWS CDKでは、下位のリソースから順に作成します。

今回は

  1. S3
  2. IAM Managed Policy
  3. IAM Role
  4. Lambda
  5. サブのStatemMachine (Step Functions)
  6. メインのStatemMachine (Step Functions)

の順で作成します。

 
Pythonでは、 core.Stack を継承したクラスの __init__() にて、作成したいリソースのオブジェクトを生成します。

class StepFunctionsStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        # S3バケットを生成
        self.bucket = self.create_s3_bucket()
        # 管理ポリシーを生成
        self.managed_policy = self.create_managed_policy()
        ...

 

S3バケットの作成

ドキュメントに従い、S3バケットを新規作成します。
class Bucket (construct) · AWS CDK

 
ドキュメントはTypeScript形式で書かれています。

new Bucket(scope: Construct, id: string, props?: Bucket<wbr>Props)

Pythonに読み替えた時の書き方です。

def create_s3_bucket(self):
    return Bucket(
        self,  # scope
        'S3 Bucket',  # id
        bucket_name=f'sfn-bucket-by-aws-cdk',  # propsのbucketName
    )

Pythonで読み替える時のポイントは以下です。

  • scopeは self で読み替え
  • 引数名は、snake_caseで読み替え
  • props にて指定可能なキーと値は、ドキュメントの Construct Props に記載

 
ちなみに、もし既存のS3バケットを利用したい場合は

  • fromBucketArn()
  • fromBucketAttributes()
  • fromBucketName()

などの静的メソッドを使います。

これにより、既存のS3バケットのオブジェクトが取得できるので、他のリソースでの指定が可能になります。

 

IAM Managed Policy (管理ポリシー)の作成

今回は、S3バケットにアクセスする管理ポリシーを作成します*2
class ManagedPolicy (construct) · AWS CDK

 
Managed Policyを作成するには、

  1. PolicyStatement
  2. ManagedPolicy

の順でオブジェクトを作成します。

 

PolicyStatementの作成

ドキュメントに従い作成します。
class PolicyStatement · AWS CDK

なお、resourcesでは、上記で作成したBucketのARNを参照する必要があります。

ドキュメントにあるように、Bucketオブジェクトのプロパティ bucket_arn でARNを参照します。
https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html#properties

def create_managed_policy(self):
    statement = PolicyStatement(
        effect=Effect.ALLOW,
        actions=[
            "s3:PutObject",
        ],
        resources=[
            f'{self.bucket.bucket_arn}/*',
        ]
    )

 

ManagedPolicyの作成

こちらもドキュメント通りです。
class ManagedPolicy (construct) · AWS CDK

return ManagedPolicy(
    self,
    'Managed Policy',
    managed_policy_name='sfn_lambda_policy',
    statements=[statement],
)

 

IAMロールを作成

次に、LambdaからS3バケットへアクセスするためのIAMロールを作成します。

まずは、 ServicePrincipal オブジェクトでLambdaを指定します。
class ServicePrincipal · AWS CDK

def create_role(self):
    service_principal = ServicePrincipal('lambda.amazonaws.com')

 
次に、 Role オブジェクトを作成します。
class Role (construct) · AWS CDK

ポイントは以下です。

  • assumed_by に、ServicePrincipalオブジェクトを指定
  • managed_policies に、作成したManaged Policyを指定
return Role(
    self,
    'Role',
    assumed_by=service_principal,
    role_name='sfn_lambda_role',
    managed_policies=[self.managed_policy],
)

 

Lambdaの作成

今回は4つのLambdaを作成します。

Lambda名 用途
sfn_first_lambda S3にファイルを保存
sfn_second_lambda サブStateMachineの1番目のLambda
sfn_third_lambda サブStateMachineの2番目のLambda
sfn_error_lambda sfn_second_lambda・sfn_third_lambdaでエラーが起きた時に実行されるLambda

 
なお、CDKで使うLambda本体は、どこかのディレクトリに入れておけばOKです。

CDKのFunctionオブジェクトを生成する際に、そのディレクトリパスを指定することで、CDKがzip化・アップロードまで面倒を見てくれます。

 

S3にファイルを保存するLambdaを作成
Lambda本体

lambda_function/first/lambda_function.py として作成します。

ポイントは以下です。

  • boto3でS3へアップロードする
  • NumPyを使って値を取得する
  • 環境変数 BUCKET_NAME で、保存先のS3バケット名を受け取る
  • Lambdaのパラメータとして message が渡されてくる
    • StateMachineのInputより渡されることを想定
  • 戻り地は、 bodymessage を持つdict
import os
import boto3
from numpy.random import rand


def lambda_handler(event, context):
    body = f'{event["message"]} \n value: {rand()}'
    client = boto3.client('s3')
    client.put_object(
        Bucket=os.environ['BUCKET_NAME'],
        Key='sfn_first.txt',
        Body=body,
    )

    return {
        'body': body,
        'message': event['message'],
    }

 

CDKでLambdaリソース(Function)を作成

まずは、Lambda本体のあるファイルパスを使って、AssetCodeオブジェクトを生成します。
class AssetCode · AWS CDK

function_path = str(self.lambda_path_base.joinpath('first'))
code = AssetCode(function_path)

 
次にNumPyを用意します。

Lambdaでは、NumPyのようによく使われるモジュールは Lambda Layers として用意されていますので、今回もこちらを利用します。
新機能 – AWS Lambda :あらゆるプログラム言語への対応と一般的なコンポーネントの共有 | Amazon Web Services ブログ

 
LayerVersionオブジェクトにて、既存のLambda Layersを扱えます。
class LayerVersion (construct) · AWS CDK

今回はARNを指定してNumPyのLayerを取得するため、静的メソッド from_layer_version_arn() を使います。

scipy_layer = LayerVersion.from_layer_version_arn(
    self, f'sfn_scipy_layer_for_first', AWS_SCIPY_ARN)

なお、Lambda LayersのARNについては、AWSアカウントごとに異なるようです。

そのため、一度、Lambda Consoleにて、該当LayerのARNを確認する必要があります。

 
最後に、FunctionオブジェクトでLambdaリソースを生成します。
class Function (construct) · AWS CDK

return Function(
    self,
    f'id_first',
    # Lambda本体のソースコードがあるディレクトリを指定
    code=code,
    # Lambda本体のハンドラ名を指定
    handler='lambda_function.lambda_handler',
    # ランタイムの指定
    runtime=Runtime.PYTHON_3_7,
    # 環境変数の設定
    environment={'BUCKET_NAME': self.bucket.bucket_name},
    function_name='sfn_first_lambda',
    layers=[scipy_layer],
    memory_size=128,
    role=self.role,
    timeout=core.Duration.seconds(10),
)

 
ちなみに、Layerを自分で作成したい場合は以下のようにします。

Codeオブジェクトのリファレンスは以下です。
class Code · AWS CDK

LayerVersion(
    self,
    'layer_id',
    code=Code.from_asset('your_zip_filepath'),
    compatible_runtimes=[Runtime.PYTHON_3_7],
    layer_version_name='layer_version_name',
)

 

残りのLambda
2番目のLambda本体

エラーハンドリングしたいため、パラレル番号が偶数の場合はエラーとしています。

また、 result_path で結果をわかりやすく表示したいため、戻り値も絞っています。

def lambda_handler(event, context):
    if event['parallel_no'] % 2 == 0:
        raise Exception('偶数です')

    return {
        'message': event['message'],
        'const_value': event['const_value']
    }

 

3番目のLambda本体

こちらもエラーハンドリングしたいため、パラレル番号が1の場合はエラーとしています。

また、最後は文字列を返すようにしました。

def lambda_handler(event, context):
    if event['parallel_no'] == 1:
        raise Exception('強制的にエラーとします')

    return 'only 3rd message.'

 

エラーハンドリングのLambda本体

タスクでエラーが発生した場合は

{
  "resource": "arn:aws:lambda:region:id:function:sfn_error_lambda",
  "input": {
    "Error": "Exception",
    "Cause": "{\"errorMessage\": \"\\u5076\\u6570\\u3067\\u3059\",
               \"errorType\": \"Exception\",
               \"stackTrace\": [\"  File \\\"/var/task/lambda_function.py\\\", line 5,
                  in lambda_handler\\n    raise Exception('\\u5076\\u6570\\u3067\\u3059')
              \\n\"]}"
  },
  "timeoutInSeconds": null
}

という値が渡されてきます。

この中の CauseJSON文字列のため、Lambdaで見えるようにして返します。

def lambda_handler(event, context):
    return {
        # JSONをPythonオブジェクト化することで、文字化けを直す
        'error_message': json.loads(event['Cause']),
    }

 

Functionオブジェクト

ほぼ同じ内容なので一つのメソッドで生成しています。

def create_other_lambda(self, function_name):
    function_path = str(self.lambda_path_base.joinpath(function_name))

    return Function(
        self,
        f'id_{function_name}',
        code=AssetCode(function_path),
        handler='lambda_function.lambda_handler',
        runtime=Runtime.PYTHON_3_7,
        function_name=f'sfn_{function_name}_lambda',
        memory_size=128,
        timeout=core.Duration.seconds(10),
    )
    
# 使う時
self.second_lambda = self.create_other_lambda('second')
self.third_lambda = self.create_other_lambda('third')
self.error_lambda = self.create_other_lambda('error')

 

サブのStateMachine (Step Functions) 作成

概要 (InputPath・OutputPath・ResultPathを試す)

ここまででパラレル実行する、サブのStateMachineのリソースが用意できました。

それらを組み合わせて、サブのStateMachineを作成していきます。

 
StateMachineでは、タスクという単位で処理を定義します。
タスク - AWS Step Functions

サブのStateMachineでは3つのタスクを用意します。

今回、InputPath・OutputPath・ResultPathを試そうと考えました。

 
そこで、こんな感じの設定にしました。

タスク名 Lambda InputPath ResultPath OutputPath
Second Task sfn_second_lambda $['first_result', 'parallel_no', 'message', 'context_name', 'const_value] $.second_result' ['second_result', 'parallel_no']
Third Task sfn_third_lambda $ (Lambdaの戻り値で上書き)
Error Task sfn_error_lambda

 
Second Taskでは、

  • InputPath
    • 前のタスクの結果から 'first_result', 'parallel_no', 'message', 'context_name', 'const_value だけ受け取って処理する
  • OutPath
    • Lambdaの戻り値を、入力値に second_result という項目を追加する
  • ResultPath
    • 次のタスクには 'second_result', 'parallel_no' のみ渡す

とします。

 
また、Third Taskでは、

  • ResultPath
    • 入力値をすべて捨て、Lambdaの戻り値だけを出力する

とします。

 

実際のソースコード

まず、複数タスクで使うため、1つのエラータスクを作成します。

Lambdaを起動するためにはInvokeFunctionクラスを使います。引数としてLambdaオブジェクトを設定します。
class InvokeFunction · AWS CDK

error_task = Task(
    self,
    'Error Task',
    task=InvokeFunction(self.error_lambda),
)

 
次に、Second Taskを生成します。

InputPath・OutputPath・ResultPathに対応する引数があるため、それぞれ指定します。

second_task = Task(
    self,
    'Second Task',
    task=InvokeFunction(self.second_lambda),

    # 渡されてきた項目を絞ってLambdaに渡す
    input_path="$['first_result', 'parallel_no', 'message', 'context_name', 'const_value]",

    # 結果は second_result という項目に入れる
    result_path='$.second_result',

    # 次のタスクに渡す項目は絞る
    output_path="$['second_result', 'parallel_no']"
)

 
Second Taskの中でエラーが発生した場合のハンドリングをするため、 add_catch でエラーが発生した時のタスクを追加します。
エラー処理 - AWS Step Functions

second_task.add_catch(error_task, errors=['States.ALL'])

 
Third Taskを作成します。こちらもエラーハンドリングを追加します。

third_task = Task(
    self,
    'Third Task',
    task=InvokeFunction(self.third_lambda),

    # third_lambdaの結果だけに差し替え
    result_path='$',
)
# こちらもエラーハンドリングを追加
third_task.add_catch(error_task, errors=['States.ALL'])

 
次に、Second Taskの後にThird Taskを実行できるよう、 next() にて設定します。

definition = second_task.next(third_task)

 
最後に、StateMachineを作成します。

return StateMachine(
    self,
    'Sub StateMachine',
    definition=definition,
    state_machine_name='sfn_sub_state_machine',
)

 

メインのStateMachine (Step Functions) の作成

最後のリソースとして、メインのStateMachineを作成します。

 
まずは First Task を作成します。ここは先程と同じです。

first_task = Task(
    self,
    'S3 Lambda Task',
    task=InvokeFunction(self.first_lambda, payload={'message': 'Hello world'}),
    comment='Main StateMachine',
)

 
次のタスクがパラレル実行するサブのStateMachineです。

最初にParallelオブジェクトを生成します。
class Parallel (construct) · AWS CDK

parallel_task = Parallel(
    self,
    'Parallel Task',
)

 
次に、パラレル実行の設定を行います。流れは以下となります。

  1. StartExecution オブジェクトで、使用するStateMachineやInputなどを指定します。
    class StartExecution · AWS CDK

  2. StartExecutionTask に渡します。

  3. TaskParallel.branch() に渡します。

for i in range(1, 4):
    # 1.
    sub_task = StartExecution(
        self.sub_state_machine,
        input={
            'parallel_no': i,
            'first_result.$': '$',

            # first_taskのレスポンスにある、messageをセット
            'message.$': '$.message',

            # コンテキストオブジェクトの名前をセット
            'context_name.$': '$$.State.Name',
            # 固定値を2つ追加(ただ、Taskのinputでignore_valueは無視)
            'const_value': 'ham',
            'ignore_value': 'ignore',
        },
    )

    # 2.
    invoke_sub_task = Task(
        self,
        f'Sub Task {i}',
        task=sub_task,
    )

    # 3.
    parallel_task.branch(invoke_sub_task)

 

以上でStepFunctionsが完成しました。

 

$と$$について

上記例では、

  • 'message.$': '$.message'
  • 'context_name.$': '$$.State.Name'

としている部分がありました。

$$$ については、それぞれ以下の意味となります。

$ について

パスを使用して値を選択するキーと値のペアの場合、キーの名前は .$ で終わる必要があります。 InputPath およびパラメータ - AWS Step Functions

 
$$ について

コンテキストオブジェクトにアクセスするには、パスを使用して状態の入力を選択したときと同様に、.$ を末尾に追加したパラメータ名をまず指定します。次に、入力の代わりにコンテキストオブジェクトデータにアクセスするには、$$. をパスの先頭に追加します。

コンテキストオブジェクト - AWS Step Functions

 

実行結果

ではStepFunctionsのConsoleより実行してみます。

 

メインのStateMachine

f:id:thinkAmi:20190929170656p:plain:w300

S3 Lambda TaskのLambdaFunctionScheduled

{
  "resource": "arn:aws:lambda:region:account_id:function:sfn_first_lambda",
  "input": {
    "message": "Hello world"
  },
  "timeoutInSeconds": null
}

S3 Lambda TaskのTaskStateExited

{
  "name": "S3 Lambda Task",
  "output": {
    "body": "Hello world \n value: 0.035671270119142284",
    "message": "Hello world"
  }
}

 

サブのStateMachine

3パターンあるため、それぞれ記載します。

  • すべて成功
  • Second Taskでエラー
  • Third Taskでエラー
すべて成功

f:id:thinkAmi:20190929171108p:plain:w300

Second TaskのLambdaFunctionScheduledでは、設定した

input_path="$['first_result', 'parallel_no', 'message', 'context_name', 'const_value']",

の通りのinputとなっています。

{
  "resource": "arn:aws:lambda:region:account_id:function:sfn_second_lambda",
  "input": {
    "first_result": {
      "body": "Hello world \n value: 0.035671270119142284",
      "message": "Hello world"
    },
    "parallel_no": 3,
    "message": "Hello world",
    "context_name": "Sub Task 3",
    "const_value": "ham"
  },
  "timeoutInSeconds": null
}

 
Second TaskのTaskStateExitedでも、

result_path='$.second_result',
output_path="$['second_result', 'parallel_no']"

の通りのoutputです。

{
  "name": "Second Task",
  "output": {
    "second_result": {
      "message": "Hello world",
      "const_value": "ham"
    },
    "parallel_no": 3
  }
}

 

Third TaskのLambdaFunctionScheduledはこんな感じ。Secondのoutputを引き継いでいます。

{
  "resource": "arn:aws:lambda:region:account_id:function:sfn_third_lambda",
  "input": {
    "second_result": {
      "message": "Hello world",
      "const_value": "ham"
    },
    "parallel_no": 3
  },
  "timeoutInSeconds": null
}

 

Third TaskのTaskStateExited

result_path='$',通り、outputはLambdaの戻り値の文字列だけになっています。

{
  "name": "Third Task",
  "output": "only 3rd message."
}

 

Second Taskでエラー

f:id:thinkAmi:20190929172018p:plain:w300

Second TaskのLambdaFunctionScheduledは以下の通り(抜粋)。

{
  "input": {
    "parallel_no": 2,
    "context_name": "Sub Task 2",
  },
}

 
Error TaskのTaskStateExitedを確認します。日本語がそのまま表示されています。

{
  "name": "Error Task",
  "output": {
    "error_message": {
      "errorMessage": "偶数です",
      "errorType": "Exception",
      "stackTrace": [
        "  File \"/var/task/lambda_function.py\", line 3, in lambda_handler\n    raise Exception('偶数です')\n"
      ]
    }
  }
}

 

Third Taskでエラー

f:id:thinkAmi:20190929172322p:plain:w300

Third TaskのLambdaFunctionScheduledは以下の通り(抜粋)。

{
    "parallel_no": 1
  },
}

 
Error TaskのTaskStateExitedを確認します。日本語がそのまま表示されています。

{
  "output": {
    "error_message": {
      "errorMessage": "強制的にエラーとします",
      "errorType": "Exception",
      "stackTrace": [
        "  File \"/var/task/lambda_function.py\", line 3, in lambda_handler\n    raise Exception('強制的にエラーとします')\n"
      ]
    }
  }
}

 

削除

cdk destroy にて削除できます。

$ cdk destroy
Are you sure you want to delete: step-functions (y/n)? y
...
 ✅  step-functions: destroyed

 

ただ、上記の方法で作成したS3は、removalpolicyの値により削除されません。
https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html#removalpolicy

そのため、手動で削除するか、作成する時に以下のような設定を行います。
AWS CloudFormationでStackを削除したときにリソースを消さない設定 | DevelopersIO

 

動的並列処理について

先日、StepFunctionsでの動的並列処理がサポートされました。
AWS Step Functions がワークフローでの動的並列処理のサポートを追加

 
CDKでは、以下のissueがcloseとなれば利用できそうです。

 

ソースコード

GitHubに上げました。 step_functions ディレクトリが今回のファイルです。
https://github.com/thinkAmi-sandbox/AWS_CDK-sample

*1:例えばLambdaの場合は、 aws_cdk.aws_lambda が必要です:https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-readme.html

*2:もし、ポリシーを使う場合はこちら:https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.Policy.html

#pyconjp 発表「知ろう!使おう!HDF5ファイル!」の落ち穂拾い

PyCon JP 2019にて発表をした際、いくつか質問をいただきました。

前回の記事にもあるように、当時きちんと回答できたか不安だったため、今回落ち穂拾いとしてまとめてみます。

なお、誤りや過不足などがありましたら、ご指摘いただけるとありがたいです。

   
目次

 

そもそも、何のデータを入れるためにHDFができたのか

The HDF Groupの History of HDF Group ページに記載がありましたので、引用します。
History of HDF Group

In 1987, the Graphics Foundations Task Force (GFTF) at the National Center for Supercomputing Applications (NCSA) at the University of Illinois at Urbana-Champaign, set out to create an architecture-independent software library and file format to address the need to move scientific data among the many different computing platforms in use at NCSA at that time. Additional goals for the format and library included the ability to store and access large objects efficiently, the ability to store many objects of different types together in one container, the ability to grow the format to accommodate new types of objects and object metadata, and the ability to access the stored data with both C and Fortran programs.

 

クロスプラットフォーム間で、Excelファイルの罫線や内容が壊れないか

発表時点でも検証していましたが、もう少し詳しく検証してみました。

用意したExcelファイルは、以下の(a)〜(d)の4種類です。

 
これらのファイルをHDF5ファイルにDatasetとして保存して、Mac/Windowsの双方で読み込んでみます。

 

自作ファイルの作成について

自作ファイルは

  • 罫線
  • 画像
  • 罫線
  • シートのロック

を持つExcelファイルを、 Mac + openpyxlで作成するものとします。

excel_creator.py として用意します。

import datetime
import pathlib

import openpyxl
from openpyxl.comments import Comment
from openpyxl.styles.borders import Border, Side, BORDER_THIN
from openpyxl.styles.colors import BLUE

BASE_DIR = pathlib.Path(__file__).resolve().parents[0]
INPUT_DIR = BASE_DIR.joinpath('input')
OUTPUT_DIR = BASE_DIR.joinpath('output', 'h5py')


wb = openpyxl.Workbook()

# grab the active worksheet
ws = wb.active

# セルにデータを入れる
ws['A1'] = 'Hello, world!'

# 日付データを入れる
ws['A2'] = datetime.datetime.now()

# 罫線を引く
side = Side(style=BORDER_THIN, color=BLUE)
border = Border(top=side, bottom=side, left=side, right=side)
a4 = ws['B3'].border = border

# コメントを入れる
ws['B4'].comment = Comment('ham', 'myauthor')

# 画像ファイル差し込み
img_path = INPUT_DIR.joinpath('shinanogold.png')
img = openpyxl.drawing.image.Image(img_path)
ws.add_image(img, 'C2')

# シートの保護
ws.protection.enable()

# 保存
output = OUTPUT_DIR.joinpath('original.xlsx')
wb.save(output)

 

公開ファイルの利用について

公開ファイルは xls 形式でした。

そのため、Mac上で手動で xlsx へと変換・保存したものを使用します。

 

HDF5ファイルとして保存するプログラム

h5pyを使い、 gleaning.py として作成します。

なお、いずれのExcelファイルも gzip で圧縮するものとします (create_dataset()の引数 compression を利用)。

import hashlib
import pathlib

import h5py

import numpy as np

BASE_DIR = pathlib.Path(__file__).resolve().parents[0]
INPUT_DIR = BASE_DIR.joinpath('input')
OUTPUT_DIR = BASE_DIR.joinpath('output', 'h5py')
TYPE_OF_BINARY = h5py.special_dtype(vlen=np.dtype('uint8'))

ORIGINAL_EXCEL_PATH = OUTPUT_DIR.joinpath('original.xlsx')
GLEANING_HDF5_PATH = OUTPUT_DIR.joinpath('gleaning.h5')

STATISTICS_3MB = OUTPUT_DIR.joinpath('statistics_kenporonbun-toukei201604.xlsx')
STATISTICS_LINE = OUTPUT_DIR.joinpath('statistics_nenpou09.xlsx')
STATISTICS_IMAGE = OUTPUT_DIR.joinpath('statistics_2017_1_02.xlsx')


def calculate_md5(path):
    return hashlib.md5(path.read_bytes()).hexdigest()


def compare_excel(from_path):
    print(f'{from_path.name} --------->')

    # 元ExcelのMD5を見る
    from_md5 = calculate_md5(from_path)
    print(f'from MD5: {from_md5}')

    dataset_name = f'excel_{from_path.stem}'

    # gzip圧縮して、ExcelをHDF5ファイルに入れる
    with h5py.File(GLEANING_HDF5_PATH, mode='a') as file:
        with from_path.open(mode='rb') as excel:
            excel_binary = excel.read()

        excel_data = np.frombuffer(excel_binary, dtype='uint8')
        ds_excel = file.create_dataset(
            dataset_name, excel_data.shape, dtype=TYPE_OF_BINARY, compression='gzip')
        ds_excel[0] = excel_data

    # 取り出して、MD5を見る
    with h5py.File(GLEANING_HDF5_PATH, mode='r') as file:
        dataset = file[dataset_name]
        export_path = from_path.parents[0].joinpath(f'{from_path.stem}_after{from_path.suffix}')
        with export_path.open('wb') as w:
            w.write(dataset[0])

    to_md5 = calculate_md5(export_path)
    print(f'to MD5  : {to_md5}')

    assert from_md5 == to_md5


if __name__ == '__main__':
    # きれいなHDF5ファイルを使うため、事前に削除しておく
    if GLEANING_HDF5_PATH.exists():
        GLEANING_HDF5_PATH.unlink()

    compare_excel(ORIGINAL_EXCEL_PATH)
    compare_excel(STATISTICS_3MB)
    compare_excel(STATISTICS_LINE)
    compare_excel(STATISTICS_IMAGE)

 

HDF5からExcelの入ったDatasetを読み込み、ローカルに保存するプログラム

こちらも h5py を使って excel_reader.py として作成します。

なお、Mac/Windows間で保存したファイルに差異がないか検証するため、MD5ハッシュをprintします。

import hashlib
import pathlib

import h5py

BASE_DIR = pathlib.Path(__file__).resolve().parents[0]
OUTPUT_DIR = BASE_DIR.joinpath('output', 'h5py')
ORIGINAL_EXCEL_PATH = OUTPUT_DIR.joinpath('original.xlsx')
GLEANING_HDF5_PATH = OUTPUT_DIR.joinpath('gleaning.h5')

STATISTICS_3MB = OUTPUT_DIR.joinpath('statistics_kenporonbun-toukei201604.xlsx')
STATISTICS_LINE = OUTPUT_DIR.joinpath('statistics_nenpou09.xlsx')
STATISTICS_IMAGE = OUTPUT_DIR.joinpath('statistics_2017_1_02.xlsx')


def calculate_md5(path):
    return hashlib.md5(path.read_bytes()).hexdigest()


def print_md5(path):

    dataset_name = f'excel_{path.stem}'
    with h5py.File(GLEANING_HDF5_PATH, mode='r') as file:
        dataset = file[dataset_name]
        export_path = path.parents[0].joinpath(f'{path.stem}_reader{path.suffix}')
        with export_path.open('wb') as w:
            w.write(dataset[0])

    to_md5 = calculate_md5(export_path)
    print(f'{path.name} MD5  : {to_md5}')


if __name__ == '__main__':
    print_md5(ORIGINAL_EXCEL_PATH)
    print_md5(STATISTICS_3MB)
    print_md5(STATISTICS_LINE)
    print_md5(STATISTICS_IMAGE)

 

MacでHDF5ファイルからExcelを取得した結果

MD5は以下の通りでした。

$ python excel_reader.py 
original.xlsx MD5  : af366474fc7f09cc5bcfdff764acf01d
statistics_kenporonbun-toukei201604.xlsx MD5  : bb82c493c80e362da47b96b9e8b28385
statistics_nenpou09.xlsx MD5  : 4de608b9452a34caa8a76f1fe8c4b265
statistics_2017_1_02.xlsx MD5  : 838d003d262f05f7e50585feab229f5d

 
また、取得した各ファイルを開いてみましたが、壊れた様子は特にありませんでした。

 

WindowsでHDF5ファイルからExcelを取得した結果

Windowsで実行した結果は以下のスクショの通りです。

f:id:thinkAmi:20190925222701p:plain:w400

 
テキストに落としてみた結果はこちら。MD5自体の差異はなさそうです。

>python excel_reader.py
original.xlsx MD5  : af366474fc7f09cc5bcfdff764acf01d
statistics_kenporonbun-toukei201604.xlsx MD5  : bb82c493c80e362da47b96b9e8b28385
statistics_nenpou09.xlsx MD5  : 4de608b9452a34caa8a76f1fe8c4b265
statistics_2017_1_02.xlsx MD5  : 838d003d262f05f7e50585feab229f5d

 
また、実際にWindowsExcelで開いてみましたが、「ファイルが壊れています」等の表示もなく、見た感じ大丈夫そうでした。

 

圧縮したDatasetを読み込む時は、展開しないとダメか

上記のExcelファイル検証で行いましたが、Datasetを作成する際、圧縮方法としてgzipを指定しました。

一方、読み込む時のソースコードでは、gzipからの展開は特に指定していません。

そのため、h5pyを使う限りは、展開する必要はなさそうです*1。  
 

zipファイルとの違い

今回の発表は、「NumPy方面でなくてもHDF5ファイルは使える」が主題でした。そのため、zipファイルとの機能の違いは確かに気になります。

調べたところでは、

  • HDF5ファイルには、パスワード設定機能がない

がありました。

ただ、プログラムから扱うことを考えると、以下のような機能を利用できるため、HDF5ファイルの方が扱いやすいのかなという印象です。

  • Datasetに紐づくAttributeに対して検索ができる
    • HDF5ファイルを作成する段階で、後から検索しやすいようなAttributeを色々と付けておく
  • (今回の主題とは外れますがが) NumPy方面と相性が良い
    • 上記の History of HDF Group からもうかがえる。
  • 圧縮して格納したDatasetを取り出す場合、展開する必要がない

 
もし、この点について詳しい方がいらっしゃいましたら、ご指摘いただけるとありがたいです。

 

圧縮アルゴリズムについて

What kind of compression methods does HDF5 support? より引用します。

HDF5 supports gzip (deflate), Szip, n-bit, scale-offset, and shuffling (with deflate) compression filters.

 
また、その他にも以下のページに記載がありました。

 

複数人で利用する時のロックはどうなるのか

HDF5 1.10.0以降、 SWMR (Single-Writer/Multiple-Reader) が導入されています。

詳細は以下のリンクとなります。

 
ちなみに、スレッドセーフについては、こちらで触れられています。

 

HDF5ファイルをSQLライクに扱いたい

HDF5ファイルでは、SQLを知らなくてもデータを階層的に保存することができます。

一方で、慣れたSQLライクにHDF5ファイルを保存したいことがあるかもしれません。

その場合は、HDFql というライブラリがあります。
The easy way to manage HDF5 data

 
そのページでは、こんな感じの例が挙げられています。

create file myFile.h5
use file myFile.h5
create dataset myGroup/myDataset as float enable zlib values(12.4)

 
以降は、発表ではふれなかった、スライドの内容です。

   

次のステップについて

HDF5ファイルについて詳しく知りたい場合は、以下の書籍が参考になります。
Python and HDF5 - O'Reilly Media | Andrew Collette著

 
また、h5pyやPyTablesの使い方については、以下の記事や発表を見るのが良いと思います。

あるいは、SciPy 2017のチュートリアルもあります。
tomkooij/scipy2017: SciPy 2017 tutorial: HDF5 take 2: h5py and PyTables

 

公式情報

The HDF Groupでは、いろいろな公式情報があります。

HDF Forumでは質疑応答が色々とありましたので、困ったら覗いてみるのもよいかもしれないです。

 

h5servを使うための準備

h5servを使うためには、少々手間がかかりましたので、メモ代わりに記載します。

自分の端末で使う場合、hostsファイルに設定

hostsファイルのIPアドレスに対する値は、 <ファイル名>.<適当なドメイン> という形で設定します。

例えば、h5servが動作している example.com にアクセスすると、 test.h5 ファイルを操作できるようにするには、以下のような設定となります。

127.0.0.1 test.example.com

 
hostsファイルの設定が終わったら、domain オプションでドメインを指定した上で、h5servを起動します。

$ python h5serv --port=5000 --toc_name=mytoc.h5 --domain=example.com

 

操作対象のファイルを作成

中身は空で問題ないですので、作成します。

作成先は、h5servのデータディレクトリである data の中になります。

また、ファイル名は test.h5 です。

p = Path(__file__).resolve().parents[0].joinpath('h5serv', 'data', 'test.h5')
with h5py.File(p, mode='w') as f:
    pass

 

この時点での data ディレクトリ構成
$ tree
.
├── public/
├── readme.txt
└── test.h5

 

動作確認

では、実際にHDF5ファイルを扱ってみます。今回は型定義を作成するところまでです。

# 型定義を作成
str_type = {'charSet': 'H5T_CSET_UTF8',
            'class':   'H5T_STRING',
            'strPad':  'H5T_STR_NULLTERM',
            'length':  'H5T_VARIABLE'}
payload = {'type': str_type, 'shape': 1}

# h5servにPOST
res1 = requests.post(
    URL_BASE + 'datasets',
    json=payload
).json()

 
結果を確認します。

新しく test.h5 ファイルが作成されました。

├── mytoc.h5
├── public/
├── readme.txt
└── test.h5

 
また、Datasetを見ると、型定義がされていることが分かります。

f:id:thinkAmi:20190925230419p:plain:w300

 
 

HDF5ファイルのバージョンについて、もう少し

HDF5 Library Release Version Numbers より、もう少し詳しく見てみます。

HDF5ファイルのバージョンが HDF5 x.y.z となっているとすると、x, y, zのそれぞれは以下を示します。

  • x: major version number
    • ライブラリやファイルフォーマットの大きな変更あり
  • y: minor version number
    • 新しい機能によるファイルフォーマットの変更
    • 偶数はstable、奇数はdevelop
  • z: release number
    • バグ修正等ライブラリの改善、フォーマットの変更なし

 

ソースコード

この記事で使ったソースコードについては、リポジトリに追加してあります。
https://github.com/thinkAmi/PyCon_JP_2019_talk/commit/df88400b033bdd64beecb8453b1f2542d7bc33a1

*1:他の言語、他のライブラリでは異なるかもしれませんが

#pyconjp PyCon JP 2019に参加しました & 発表しました

9/16(月・祝)・17(火)に大田区産業プラザ PiOで開催された「PyCon JP 2019」に参加 & 発表しました。
トップページ - PyCon JP 2019

 
去年同様、今年も無事に参加できてよかったです。
#pyconjp PyCon JP 2018に参加しました - メモ的な思考的な

 
動画もすでに公開されています。ありがとうございます。
PyConJP - YouTube

 
ここでは自分の発表、および全体を通してのメモを残します。誤りがありましたらご指摘ください。

 
目次

 

自分の発表「知ろう!使おう!HDF5ファイル!」

タイムテーブル上の詳細はこちら
知ろう!使おう!HDF5ファイル!

 
スライドです。

 
発表で使用したソースコードはこちらです。
https://github.com/thinkAmi/PyCon_JP_2019_talk

 
動画はこちら。7分くらいから始まるようです。
02-603_知ろう!使おう!HDF5ファイル!(thinkAmi) - YouTube

 
発表ですが、時間配分を誤り、持ち時間を発表で使い切ってしまったことから、時間内に質疑応答ができませんでした。本当に申し訳ないです。

本当は「この発表の次のステップ」についてふれたかったのですが、時間の都合上カットしました。資料だとp77〜あたりです。

 
ただ、ありがたいことに、発表終了後いくつか質問をいただきました。

覚えているのは

  • そもそも、何のデータを入れるためにHDFができたのか
  • クロスプラットフォーム間で、Excelファイルの罫線や内容が壊れないか (特に大きなサイズのExcelファイル)
  • zipファイルとの違い

あたりです。

ただ発表後も緊張が解けず、質問と回答の記憶があやふやです。そのため、もしかしたら質問の内容不足や誤りがあるかもしれません。その場合はお知らせください。

また、ちょっと検証したいこともあるため、検証後にBlog化したいです。

 
というところで、以降は今回のPyCon JP 2019のメモです。

 

参加したセッション

基調講演:Why Python is Eating the World

Cory Althoff 氏

 
書籍「独学プログラマー」の著者による基調講演でした。

Think PythonPythonを学びはじめてからの行動を聞き、やはり何か気になることがあれば行動することが大事だなと感じました。

また、後半のStart somethingのところを聞き、今の自分にできることとして、このBlogを引き続き書いていこうと感じました。

 

ExcelPython による社会インフラシステムの設定ファイルの自動生成

武山 文信 氏

 
設定ファイル という単語につられて参加しました。

お話の中で、設定ファイル生成ツールに求められる条件が整理されていましたので、もし同じような状況になったら参考にしたいです。

互換性を保ちながら、できるところだけでも自動化していく姿も印象に残りました。

 

Djangoで実践ドメイン駆動設計による実装

大島 和輝 氏

 
Djangoでのドメイン駆動設計が気になって参加しました。

セッションの中でもふれられていましたが、Djangoでのドメイン駆動設計例が見当たらないため、今回のソースコードを参考にして取り組んでみたいです。

Python製のDIコンテナ python-inject にも言及しており、後で見てみようと思いました。

 

SupportingPython3 in Large Scale Project

Kir Chou 氏

 

Python2からPython3に切り替えるにあたり、考えるべきことがいくつもあげられていました。

また、技術的な資料へのリンクも豊富にあるのが助かりました。

今のところ手元には移行するものはありませんが、今後出会う機会があれば、参考にしたいと感じました。

 

Python ウェブアプリケーションのためのプロファイラの実装

Yusuke Miyazaki 氏

 
昔、WSGIミドルウェアについて調べていた時に、 wsgi-lineprof を試したことがあったため、内部構造などを知りたくて参加しました。

wsgi-lineprofのアーキテクチャの図が分かりやすかったです。

また、プロファイラはどのような技術で構成されているかの紹介もあり、こんな便利な機能がいろいろとあるんだと勉強になりました。

 

Doujin-activity with Ren'Py

Daisuke Saito 氏

 
来週の技術書典7に参加予定なこともあり、 Doujin というタイトルに惹かれて参加しました。

Ren'Pyの概要と、簡単にゲームができあがるということが分かりました。

 

基調講演:Pythonで切り開く新しい農業

小池 誠 氏

 
きゅうりで機械学習 という話を読んだこともあり、楽しみな基調講演でした。

初めて見た時とは判別機の扱いが変わっていて、判別機が人間のサポートをするようにしたことで、熟練者の技術継承がしやすくなっているように見えました。

講演の中で特に印象に残ったのは、終わりの方の すべては学ぶためにとすれば、失敗ではなくなる。やってみなければわからない という言葉です。

それを聞き、次に活かせるような形で失敗するよう心がけようと思いました。

 

Pythonで始めてみよう関数型プログラミング

寺嶋 哲 氏

 
関数型プログラミングについて、その考え方とPythonで実現するためのライブラリ紹介がありました。

自分の今のコードに取り入れることができそうな関数型の考え方がいくつもあり、参考になりました。

あとで資料を見返して、理解を深めたいと感じました。

 

Pythonでライブをしよう - FoxDotを使った新時代のPython活用法 -

田中 慎太郎 氏

 
同僚です。

社内のPyCon JP 予行演習会にてライブコーディングを聴いた時から「当日は、この次のコマで自分が発表するから、この音楽セッションでリラックスしよう」と考えていました。

デモが進むに従って曲になっていったので、「あの変更でこんなふうに音が変化するんだ」と、とても不思議な感覚でした。音楽に詳しくないせいかもしれませんが、新鮮でした。

Youtube上だとFoxDotの音があまり残っていないようで残念ですが、ライブだと思えば仕方ないですね。。。

 

機械学習ライブラリのPython API作成方法

山入端 孝浩 氏

 

外部ライブラリのラッパーを作る時の大切なこと(既存との互換性やメモリ使用量など)についてふれられていました。

普通に使う時とpytestの時でメモリ使用量が異なるの、あまり着目したことがなかったので、参考になりました。

 

Ansibleを通じて「べき等性」を理解してみよう

Kazuya Takei 氏

 
Ansibleではどんなふうに冪等性を担保しているのか気になっていたこともあり、参加しました。

ソースコードの解説から、Ansibleががんばって標準出力を解析してべき等性を担保していると分かり、意外と泥臭いことをやってると分かりました。

あとは、Ansibleで管理できるルーターがほしいなーと思いました。

 

その他

同僚のスピーカーなど

上記にもありますが、私を含めて、会社から3名がスピーカーとして登壇しました。もう一人はこちら。
Pythonと便利ガジェット、サービス、ツールを使ってセンシング〜見る化してみよう

 
また、今年もぷろぷろ(@pro_proletariat)氏の呼びかけで、社内でPyCon JP 予行演習会が何回か開催されました。ありがとうございました。

 
あと、社内のPython使いの絵師さんが描いたマスコットキャラクター(ぎーらぼちゃん)も、無事デビューできてよかったです。

 

ブース

2日目の登壇ということもあり、精神的余裕があまりなく、ブース巡りはあまりできなかったのが残念でした。

1日目のはじめに、Python EDのガラガラでマグカップをいただきました。ありがとうございました。

 
また、書籍「独学プログラマー」のサイン会がありました。めったにない機会なので書籍を購入しサインを頂きました。ありがとうございました。

 

パーティ

ご挨拶したかった方々といろいろとお話できてよかったです。

Welcomeパーティの時もそうでしたが、年々パーティの雰囲気に慣れてきているのか、今年は自分から話しかけられたので成長した感がありました。

 
最後になりましたが、PyCon JP 2019を運営してくださったみなさま、参加者のみなさま、ありがとうございました。

来年も同じ場所で開催されるようですので、楽しみにして一年を過ごしたいと思います!

#agileshinano #glnagano Agile Jam in ながのに参加しました

8/24にギークラボ長野で開かれた「Agile Jam in ながの」に参加しました。
Agile Jam in ながの - connpass

 
平日夜に開催されているワークショップの拡大版で、複数のスクラムマスターの方々からお話を直接聞けるなど、とても有意義なイベントでした。

以下、簡単なメモです。

 
目次

 

オープニング・セッション

@shigeshibu44さん、@chinoppy0727さん

隣の方々と自己紹介をしました。

その時の手法が Moving Motivators で、自分のモチベーションがどこからやってくるのかを伝えるものでした。
Moving Motivators (CHAMPFROGS): Intrinsic Motivation Game - Management 3.0

 
自分は 好奇心熟達受容 を選びましたが、他の方々の内容を聞いて「そういう考え方もいいなー」と感じました。

そのため、Moving Motivatorsは

  • 自分の志向について、振り返りしやすい
  • チームビルディング時に、メンバー間でお互いの志向が知りやすい

など、とても良いツールだなと感じました。

 

セッション1

@itoyama_yuukiさん

全社ウォーターフォールの中、1つのプロジェクトをスクラムで始め、2年後にはほぼ全社スクラムになった流れを伝えてくださいました。

アジャイルはじめの一歩」について考えたりすることもあるので、どれも印象に残ったセッションでした。

  • スクラム導入のために
    • スクラム説明会をやる
      • 各自の役割を伝える
      • スクラムガイドを使って説明する
      • 何度も伝える
      • 自分から社内勉強会を開く
      • スクラムランチやSlackのチャンネルなど、誰もが相談しやすい環境を作る
    • 新規プロダクト or 区切りの良いところから導入する
      • 途中から導入するのは難しい
      • 既存の開発手法を無視すると宣言して始めた
        • 膨大なドキュメント作成への注力をやめる
        • 会社に受け入れる土壌があり、組織的なバックアップもあった
      • 途中から開発モデルが定義され、スムーズに
  • スクラムのプラクティスのうち、取り入れられるところがあれば取り入れる
    • 振り返りだけでもやってみる
    • 定例を開き、みんなで集まって話す
  • スクラムまわり
    • スクラムのイベントには時間を使う
    • 開発チームに対しては、リモートでスクラムをやっても問題ない
      • スクラムのイベントには、Zoomなどを使う
      • PO/SMがリモートだと難しいことかもしれない

 

セッション2

@massa142さん、@moon_in_naganoさん

資料:スクラムの現場 - slideship.com

 
セッションで一番印象に残ったのは

  • スクラム = 現状把握のためのフレームワーク
  • スクラムで大事なことは、透明性を担保すること
    • 開発に必要なものは、社員・業務委託に関わらず自由にアクセスさせる
    • 議事録を作り公開する

です。

その他は以下です。

  • スクラムのコミットメント
    • プランニングで計画を完了させるために最大限努力する
      • これがコミットメントだと、みんなの認識を合わせる
  • 見積もり
    • 工数ではなく、やることの相対的な大きさ
      • 基準チケットの何倍かかるかを見積もる
    • チケットの精度を高めるより、ベロシティが安定することが大事
      • 精度よりも、技術的な話をするのに時間を使う
  • ホワイトボードに問題点を書くことで、チーム vs ホワイトボードという構図にする
    • 個人の問題ではなくチームの問題にする
  • とりあえずやってみる
    • 分からないものについては議論しない
    • やってみた経験・学びを次に活かす
  • ポジティブ思考でやっていくの大事
    • KPTのProblemを歓迎する
  • アジャイルは、ウォーターフォールと対立するわけではない

 

ワークショップ

参加者で話し合いたい内容を挙げて、その内容について2つのテーブルに分かれて話し合うというワークショップでした。

スピーカーの方々へのQAやお互いの意見交換を行い、最後は模造紙にまとめ、ギークラボ長野の壁に張り出しました。

クロージングでは、スクラムのイベントについての紹介もありました。
Regional Scrum Gathering® Tokyo 2019

 

懇親会

場所を懇親会会場を移して、スクラムマスターの方々を交えて

などを行い、楽しい時間を過ごせました。

雰囲気です。

 
最後になりましたが、企画・運営・参加をされたみなさま、ありがとうございました。

*1:お酒が入っていたので、記憶があやしい...