tech.guitarrapc.cóm

Technical updates

AzureDevOpsPipeline/Azure DevOps Pipeline で docker build と Azure Container Repository への push を行う

Azure Pipeline を使っているとタスクによせたくなるのですが、あんまりそういうのもアレなのでほどほどにというのはもうちょっと言われてもいい気がします。 Docker はその最たる例で コマンドで3行で済むようなものがDockerタスクを使うといたずらに時間を取られる傾向にあります。

今回は Azure DevOps Pipeline でDocker ビルドを行って、Azure Container Repository に push する流れを見てみましょう。 Azure DevOps Pipeline を使っていないなら、これでやるメリットはアカウント統合程度で特にない感じです。(アカウント統合も統合できていない感がつよい)

目次

[:contents:]

TL;DR

ビルドマシンは hosted が楽でよい。 docker build は、dockerタスクではなく docker コマンドでやるほうがいい docker push は、docker タスクを使って認証を透過的に行おう。

何をDockerで動かすの

ASP.NET Core 2.2 を対象にdockerで動かしてみましょう。 例えばAzure Container Instance で動かすのもいいのです。 しかし、container じゃなくてもAzure Web Apps for Linux にデプロイすれば たいがいはいいのでWebアプリのdocker展開はポータビリティをどこまで担保したいか、どう動かしたいかの相談なのでほどほどに。

Azure DevOps Pipeline の Docker タスク

Docker Module は、0 と 1と2があります。

0 を利用するのがおすすめです。

Docker タスク0.*

1 は妙な挙動をして不安定なためお勧めしません。

Dockerタスク 1.*

2 は、コンセプトが変わっててACRで使うならあんまりいらなさそう。というか、余計な手間が増えてて微妙。

Dockerタスク2.*

ローカルでの docker build

なにはともあれlocal で通しておきましょう。 これがCIで通るようにします。

適当にこんな構成とします。 ASPNETCOREにcsprojの名前を、YOUR_PROJECT_DIR にプロジェクトのフォルダ名を指定しておきます。

.
└ src
  └ YOUR_PROJECT_DIR 
      ├ Dockerfile
      └ ASPNETCORE.csproj

Dockerfileはこのような感じになるかと思います。

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /src/YOUR_PROJECT_DIR
COPY ["YOUR_PROJECT_DIR/ASPNETCORE.csproj", "ASPNETCORE.csproj"]
RUN dotnet restore "ASPNETCORE.csproj"
WORKDIR /src
COPY . .
WORKDIR /src/YOUR_PROJECT_DIR
ARG conf="Debug"
RUN dotnet build "ASPNETCORE.csproj" -c $conf -o /app
RUN dotnet publish "ASPNETCORE.csproj" -c $conf -o /app

FROM base AS final
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "ASPNETCORE.dll"]

あとは実行時に ASPNETCORE_ENVIRONMENT を Development | Staging | Production で指定すれば挙動が切り替わるのでokですね。

docker build

特にWindows系のhosted|private agent がそうですが、build をする場合、Dockerタスクではなく docker コマンドをたたくほうが安定します。 dockerタスクで行おうとすると、docker build中にイメージが見つかったり見つからなかったり、COPYが実行できたりできなかったり構成を変えていないのに実行ごとに挙動が変わりました。(これでDockerfile や設定がおかしいかと思って試行錯誤で数時間消費した)

variables:
  BuildConfiguration: Debug
  DockerImageName: ASPNETCORE
  DockerFile: YOUR_PROJECT_DIR/Dockerfile

steps:
- bash: 'docker build -t youraspnetcoreregistry.azurecr.io/$(DockerImageName):$(Build.SourceVersion)_$(Build.BuildId) -t $(Registry).azurecr.io/$(DockerImageName):latest -f YOUR_PROJECT_DIR/Dockerfile .'
  displayName: 'docker build'

これでビルドが実行できます。 ACRに挙げるにあたって、ビルドごとにバージョンが一意に特定できる必要があるので、git hash + build id で毎ビルドで一意にしています。

docker push

push は簡単です。 local でいうところの、docker login しておいて docker push です。 ただし、CIでやるに伴ってレジストリのログインを認証情報を出さずに透過的に行いたいでしょう。

docker@0 タスク を使うと ACRへの認証情報を事前に解決しておけるので便利です。

先ほど docker build で指定した tag を指定してpush してあげましょう。 この時、ACRを対象にしている場合、ACRのアドレス "youraspnetcoreregistry.azurecr.io" 部分は自動的に解決されるため、tagで指定する必要がありません。docker build でyouraspnetcoreregistry.azurecr.io/$(DockerImageName):$(Build.SourceVersion)_$(Build.BuildId) と指定した場合、$(DockerImageName):$(Build.SourceVersion)_$(Build.BuildId) でokです。(.... なんと余計なことを)

- task: Docker@0
  displayName: 'Push an image'
  inputs:
    azureSubscription: YOUR_SUBSCRIPTION
    azureContainerRegistry: '{"loginServer":"youraspnetcoreregistry.azurecr.io", "id" : "/subscriptions/abcdefg-1234-abcd-56789ghijklmn/resourceGroups/AWESOME-RESOURCE/providers/Microsoft.ContainerRegistry/registries/youraspnetcoreregistry"}'
    action: 'Push an image'
    imageName: '$(DockerImageName):$(Build.SourceVersion)_$(Build.BuildId)'
    includeLatestTag: true

これで無事にdocker build -> push ができるでしょう。

CircleCI のOrb をPull Request を通じて学ぶ

エンジニア同士で話していると、CIどうしよう、今何がいいかなぁという話にたびたびなります。

CIサービスは複数ありますが、サーバーサイドビルドでSaaS 型CI なら CircleCI が今のところいいい感じです。(2.1を前提とする)

circleci.com

あるいはGitHub Actions もとてもいい感触です。 現状Beta で push イベント駆動なのでlintやビルドにはいい感じで利用できます。他のTagなどのイベントを利用できるようになるのが楽しみです。

github.com

今回は、CircleCI 2.1 で利用できる Orb についてPR送ることを通して学んでみます。

目次

TL;DR

PRを出したくて仕組みを理解していくと何かと捗るのでいいですよ。 実情は、適切に動かすためにコンセプトと仕様を理解することに徹するだけです。 モチベーションだいじ。

Orb とは

CircleCI 2.1 から利用できる機能で、JobsやCommand に定義していた処理を公開し、それを利用できる機能です。

circleci.com

利用方法はいたって簡単です。

circleci.com

YAML で version: 2.1 を宣言し、orbs で利用するorbを定義、command/jobs/workflow のいずれかでOrbに定義されたcommandやjob を namespace/コマンド のような記述で利用するだけです。 ドキュメントにあるミニマムな例が分かりやすいでしょう。

version: 2.1

orbs:
    hello: circleci/hello-build@0.0.5

workflows:
    "Hello Workflow":
        jobs:
          - hello/hello-build

いざ使うときに気になる「どんな Orbがあるのか、使い方は?」は、Orb Explorer を使うことで探すことができます。

circleci.com

Orbレジストリから検索

Orbを利用するにあたり困らない例と困る例

さて公開されているOrbですが、いい感じで使える一方であと一歩これが足りない、というケースがあります。

例えば、circleci/aws-s3@1.0.6 を見てみましょう。

https://circleci.com/orbs/registry/orb/circleci/aws-s3

このOrbがよく考えられているのが、arguments パラメーターを公開しておりOrbに定義がないパラメーターを受付可能にしていることです。 このパラメーターがあるおかげで、aws cli にOrbが想定していないパラメーターを渡すことができます。

version: 2.1
orbs:
  aws-s3: circleci/aws-s3@1.0.0
jobs:
  build:
    docker:
      - image: 'circleci/python:2.7'
    steps:
      - checkout
      - run: mkdir bucket && echo "lorum ipsum" > bucket/build_asset.txt
      - aws-s3/sync:
          from: bucket
          to: 's3://my-s3-bucket-name/prefix'
          arguments: |
            --acl public-read \
            --cache-control "max-age=86400"
          overwrite: true
      - aws-s3/copy:
          from: bucket/build_asset.txt
          to: 's3://my-s3-bucket-name'
          arguments: '--dryrun'

一方で、circleci/aws-code-deploy@0.0.7 を見るとこの arguments パラメーターがないために、ごにょごにょできません。

version: 2.1
orbs:
  aws-code-deploy: circleci/aws-code-deploy@1.0.0
workflows:
  deploy_application:
    jobs:
      - aws-code-deploy/deploy:
          application-name: myApplication
          deployment-group: myDeploymentGroup
          service-role-arn: myDeploymentGroupRoleARN
          bundle-bucket: myApplicationS3Bucket
          bundle-key: myS3BucketKey

AWS CLI を Circle CI で使うときの注意

aws関連のOrbに arguments を渡す口がなくて困る例として、aws cliの処理を assume role による認証で行いたい場合があります。

docs.aws.amazon.com

assume role を使うことで、認証は1つで、assume 先のrole定義でアクセス先を切り替えることができます。 また、アクセス先の認証も assume role の policy で縛ることができるため、AWS のアクセス制御的にも相性が良くなっています。 assume role を利用するには、そのプロファイル定義と role_arnsource_profile を定義します。

[profile marketingadmin]
role_arn = arn:aws:iam::123456789012:role/marketingadmin
source_profile = user1

あとは、環境変数でAWS_DEFAULT_PROFILE を定義するかAWS_PROFILE で暗黙的に解決させる、あるいはコマンド実行時に --profile プロファイル名 が必要です。

aws s3 cp .... s3://.... --marketingadmin

CircleCIの事情を考えると、circleci の aws 系のOrbは circleci/aws-cli を必ず利用しています。

https://circleci.com/orbs/registry/orb/circleci/aws-cli

この Orbは非常に使い勝手が良く、AWS_ACCESS_KEYAWS_SECRET_ACCESS_KEYAWS_DEFAULT_REGION をCircle CI のProject にあるEnvironment Variable に定義しておくだけで aws configure が行われます。

一方で、このOrbは default profile を使う前提であるため任意の Profile を追加で生成できません。そのためassume role を使うときには別途プロファイルを追加する必要があります。

      - run: 
          command: |
            mkdir -p ~/.aws
            echo "[assume_role]" >> ~/.aws/credentials
            echo "source_profile = default" >> ~/.aws/credentials
            echo "role_arn = << parameters.role_arn >>" >> ~/.aws/credentials

circleci/aws-cli を自分で実行しない場合は、事前に ~/.awsを作ってあげましょう。もしcircleci/aws-cli/installcircleci/aws-cli/configure を自分で実行するなら不要です。 あとはコマンドに Profile を渡せばいいです。 で、前節のcircleci/aws-s3 の場合は、arguments: '--profile assume_role' とOrb実行時に渡せばいいのでok、となります。

余談ですがcircle ci のaws-cli/configureを使うなら、AWS_DEFAULT_PROFILEAWS_PROFILE は使えません。 これらを定義しているとaws-cli/configure でコケます、AWS_DEFAULT_PROFILEaws configureが終わっている前提なのでまぁシカタナイ。

Traceback (most recent call last):
  File "/usr/local/bin/aws", line 27, in <module>
    sys.exit(main())
  File "/usr/local/bin/aws", line 23, in main
    return awscli.clidriver.main()
  File "/usr/local/lib/python2.7/site-packages/awscli/clidriver.py", line 59, in main
    rc = driver.main()
  File "/usr/local/lib/python2.7/site-packages/awscli/clidriver.py", line 193, in main
    command_table = self._get_command_table()
  File "/usr/local/lib/python2.7/site-packages/awscli/clidriver.py", line 102, in _get_command_table
    self._command_table = self._build_command_table()
  File "/usr/local/lib/python2.7/site-packages/awscli/clidriver.py", line 122, in _build_command_table
    command_object=self)
  File "/usr/local/lib/python2.7/site-packages/botocore/session.py", line 671, in emit
    return self._events.emit(event_name, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/botocore/hooks.py", line 356, in emit
    return self._emitter.emit(aliased_event_name, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/botocore/hooks.py", line 228, in emit
    return self._emit(event_name, kwargs)
  File "/usr/local/lib/python2.7/site-packages/botocore/hooks.py", line 211, in _emit
    response = handler(**kwargs)
  File "/usr/local/lib/python2.7/site-packages/awscli/customizations/preview.py", line 69, in mark_as_preview
    service_name=original_command.service_model.service_name,
  File "/usr/local/lib/python2.7/site-packages/awscli/clidriver.py", line 318, in service_model
    return self._get_service_model()
  File "/usr/local/lib/python2.7/site-packages/awscli/clidriver.py", line 335, in _get_service_model
    api_version = self.session.get_config_variable('api_versions').get(
  File "/usr/local/lib/python2.7/site-packages/botocore/session.py", line 233, in get_config_variable
    logical_name)
  File "/usr/local/lib/python2.7/site-packages/botocore/configprovider.py", line 226, in get_config_variable
    return provider.provide()
  File "/usr/local/lib/python2.7/site-packages/botocore/configprovider.py", line 323, in provide
    value = provider.provide()
  File "/usr/local/lib/python2.7/site-packages/botocore/configprovider.py", line 382, in provide
    config = self._session.get_scoped_config()
  File "/usr/local/lib/python2.7/site-packages/botocore/session.py", line 334, in get_scoped_config
    raise ProfileNotFound(profile=profile_name)
botocore.exceptions.ProfileNotFound: The config profile (assume_role) could not be found
Exited with code 1

circleci/aws-code-deploy で arguments 対応をPRする

このあたりを理解した上で、一時対処として自分の Commands で対応していたのですが、公式にあった方がいいので対応しました。

circleci/aws-code-deploy@0.0.9 から arguments が利用できます。

github.com

なお、#140 で対応漏れがあったので #144 をすぐに出しましたが... はずかしい、Orbのテストがないと厳しいですね。

github.com

対応は単純で arguments パラメーターをつけて回るだけです。この時、when が string型に対しては '' をfalse と認識することを利用しています。

Empty strings are treated as a falsy value in evaluation of when clauses, and all other strings are treated as truthy. Using an unquoted string value that YAML interprets as a boolean will result in a type error.

circleci.com

加えて、<<# parameters.xxxxx >> <</parameters.xxxxx>> によってパラメーターが true の場合にのみ埋め込まれるのを使うとこう書けばいいことが分かります。

<<# parameters.arguments >> << parameters.arguments >><</parameters.arguments >>

これで、argument を使って --profile を指定可能になりましたとさ、めでたしめでたし。

version: 2.1
orbs:
  aws-code-deploy: circleci/aws-code-deploy@1.0.0
workflows:
  deploy_application:
    jobs:
      - aws-code-deploy/deploy:
          application-name: myApplication
          deployment-group: myDeploymentGroup
          service-role-arn: myDeploymentGroupRoleARN
          bundle-bucket: myApplicationS3Bucket
          bundle-key: myS3BucketKey
          arguments: '--profile assume_role'

Google Analytics やGoogle Adsを使っていることの対応

このサイトでは、Google Analytics を使っています。これは、アクセスに対してどういう動きをしているのか、どんな興味を持たれているのかのサイトの改善を意図しています。あとはGoogle Analyticsがどんどん変化するので手元で確認できるサイトを持っておきたいからです。

一昨年から、Google Adsense をいれています。これは、広告を普段使っていなかったので、どういう設定でどういう埋め込みや見た目、Rewardがあるのかを確認し、それをこのサイトの運営に充てるためです。

さて、ということはクッキーポリシーに対応しないといけないわけで、怠っていたので対応を進めることにしました。

目次

対応が必要なのか

Google Analytics と Google Adsense それぞれの利用規約を見ます。

www.google.com

お客様は適切なプライバシー ポリシーを用意および遵守し、訪問者からの情報を収集するうえで、適用されるすべての法律、ポリシー、規制を遵守するものとします。お客様はプライバシー ポリシーを公開し、そのプライバシー ポリシーで、お客様がデータ収集のために Cookie を使用していることを必ず通知するものとします。また、Google アナリティクスを使用していること、および Google アナリティクスでデータが収集、処理される仕組みについても必ず開示するものとします。

Google Adsense は次がオンライン利用規約ですが、ここではポリシーの設定については記載がありません。

www.google.com

Google Adsenseはコンテンツポリシーにもう少し細かくあります。

support.google.com

サイトのプライバシー ポリシーについて プライバシー ポリシーには次の情報を記載する必要があります。

Google などの第三者配信事業者が Cookie を使用して、ユーザーがそのウェブサイトや他のウェブサイトに過去にアクセスした際の情報に基づいて広告を配信すること。 Google が広告 Cookie を使用することにより、ユーザーがそのサイトや他のサイトにアクセスした際の情報に基づいて、Google やそのパートナーが適切な広告をユーザーに表示できること。 ユーザーは、広告設定でパーソナライズ広告を無効にできること(または、www.aboutads.info にアクセスすれば、パーソナライズ広告に使われる第三者配信事業者の Cookie を無効にできること)。 第三者配信による広告掲載を無効にしていない場合、広告の配信時に第三者配信事業者や広告ネットワークの Cookie が使用される可能性があります。その点についても、次の方法でサイトのプライバシー ポリシーに明示してください。

会社、個人に関わらず利用者はなので対応します。

どう対応するのか

前提として、個人を識別する情報は取得していません。そこで、追加で必要となる「必須コンテンツの記載にある3つ」を対応します。

  • Google などの第三者配信事業者が Cookie を使用して、ユーザーがそのウェブサイトや他のウェブサイトに過去にアクセスした際の情報に基づいて広告を配信すること

  • Google が広告 Cookie を使用することにより、ユーザーがそのサイトや他のサイトにアクセスした際の情報に基づいて、Google やそのパートナーが適切な広告をユーザーに表示できること

  • ユーザーは、広告設定でパーソナライズ広告を無効にできること(または、www.aboutads.info にアクセスすれば、パーソナライズ広告に使われる第三者配信事業者の Cookie を無効にできること)

上から順に、次の対応を取ります。

  • 利用サービスの記載とアクセス情報に基づいた広告配信を行うことを示した、クッキーポリシーのページを配置
  • クッキーを利用することの通知
  • オプトアウト方法の提供

GDPRの内容からいくと、「広告表示を行うことの通知」ではたりず、初アクセス時点ではAnalyticsは無効にしておいてクッキー同意の通知でon/offの切り替えが必要そうです。このサイトは日本語配信しており、アクセス動向からも閲覧者は99.99%日本であると判明しているのでGDPRに沿った有効化/無効化対応はまた今度にします。

hyper-text.org

クッキーポリシーの配置

プライバシーポリシーが望ましいと思いますが、今このサイトではクッキーの利用のみに関してトラッキング、広告配信を行っています。そこで、まずはクッキーポリシーを配置することにします。

tech.guitarrapc.com

このクッキーポリシーは、Cookie Policy Generator: Create your Cookies Policy を用いて生成したものに、Google Analytics/Ads のコンテンツポリシーにあった Google の Policyへのリンクを載せています。

policies.google.com

policies.google.com

また、クッキーポリシーはサイトのLinks 内にアクセスするためのリンクを張っておいていつでも確認できるようにしておきます。

クッキーを利用することの通知

どのような内容にするかわからず調べました。情報があまり一貫性がないようですが、GDPRに対応するなら同意をとってから取得するためのバナー対応に踏み込んでおく方がよさそうです。*1

どんな表示がいいのかGoogleの解説を見ます。

サイト運営者様のサイトや法律は国ごとに異なるため、Google でプライバシー ポリシーに関する具体的な文面を提案することはできません。サイトのプライバシー ポリシーを作成する際には、Network Advertising Initiative などのリソースを参考にされることをおすすめします。Cookie 使用の同意を得るための情報通知に関する詳細については、cookiechoices.org をご覧ください。

クッキーの同意に関しては、cookiechoices.org を見るのが良いとあります。

www.cookiechoices.org

この中で、「自社サイトへのアクセスデータを別の場所での広告配信に利用しない場合は、次のような通知が適するかもしれません。」があります。

自社サイトへのアクセスデータを別の場所での広告配信に利用しない場合の通知例

幸い、本サイトはあくまでも自分の勉強結果を載せているだけでこのサイトから他のサイトに連携したり、別の場所での広告配信に利用することはありません。クッキーの利用を通知するの通知 + 先ほど設置したクッキーポリシーへの誘導でよさそうです。

このような勉強目的の情報だけ載せているサイトで、全画面で同意をとったりしてみるものが見えないのは最悪だと思います。そこで、画面下にバナー通知を出すことにします。この通知は Cookie Concent と呼ばれ、書く方法や生成サイトが複数あります。今回は、デモが自分の希望する体験に近く、コードも小さいinsites が提供するものを利用します。

cookieconsent.insites.com

表示方法や配色、リンク先を調整したら、コードをデザイン画面で配置します。

オプトアウト方法の提供

先ほどのクッキーポリシーですが、生成した時点ではOpt Out がついていません。 Required content - Google AdSense Help に基づくと、オプトアウト提供方法について記載があります。

ユーザーは、広告設定でパーソナライズ広告を無効にできること(または、www.aboutads.info にアクセスすれば、パーソナライズ広告に使われる第三者配信事業者の Cookie を無効にできること)。

これをクッキーポリシーに提示しておけば広告の除外、オプトアウトがユーザーそれぞれが希望に応じて設定できます。

adssettings.google.com

WebChoices: Digital Advertising Alliance's Consumer Choice Tool for Web US

結果確認

次の結果になればよさそうです。

  • プライベートブラウズでアクセスしたときに、サイト下部にクッキーに関するバナーが表示される
  • バナーのリンクからクッキーポリシーにアクセスできる
  • クッキーポリシーのOpt out リンクからGoogle の広告設定ページやWebChoices にアクセスできる

クッキーはPC、スマホともに問題なさそうです。

PCのプライベートブラウズでクッキーに関するバナーが表示される

スマホでもクッキーに関するバナーが表示される

バナーのリンクも無事に遷移しました。

Learn more から Cookie Policyページへ遷移

Opt out も遷移しました。

optout.aboutads.info への遷移

Your Adchoiceの確認

今後

クッキーポリシーが英語なのは、結局それでいいかなぁという怠りなので、日本語にするか考えます。やらない気もします。

GDPRに対応したり、ほか媒体で利用するようなケースはECサイトなど企業での利用ではありそうです。Google アナリティクスの広告向け機能に関するポリシー要件には、欧州連合対応があり同意をもとめるかの商業的対応が必要とあるのでこのあたりを使うなら必須ですね。

support.google.com

Google Analytics は、UAの有効化/無効化対応でもよさそうですがGoogle Ads はどうするのが適切なんですかね? というあたりがよくわからない感じです。

このあたり、まとめがほしい。

余談

今回このような表示を行うにあたって、バナー通知を目立たせるために記事とバナーだけを表示できるワンカラムへの変更、バナーがマテリアル風だったのでサイトのみため調整もしてみました。

たまった記事かかなきゃ。

*1:クリック回数的にも読むのにつらくない

AzureFunctions におけるStorageTableBindingの選択

Azureには、Storage Table という機能があり単純なテーブル形式でデータが管理できます。 C# のコード的にはTableEntity を継承してデータを表現し、メソッドを組み立ててCRUDを実現できます。

さて、このStorage TableをWebJobs 並びにその実装の1つである Azure Functions で使うときについて考えてみましょう。

目次

TL;DR

Bindingには複数の課題があり、特にStorage Table や DBのバインディングはパラメーターが多く問題が起きやすい。

  • Bindingは属性 + エントリーポイントのエラーのためStackTraceが取れないケースが多い
  • 加えて Binding のリフレクション多用による難しさ
  • StorageTableとDBは特にパラメーターが多くBinding問題に当たりやすい
  • NuGetバージョンのRegression耐性の乏しさ

このため、Storage TableやDBに関してはBindingはやめてFunction内でクライアントを自前で作って安定させるほうが良いケースが多い。

他のHttpTriggerやQueue などはシンプルでトラブルが起こりにくく、自前で定型的に実装することになる機能がサポートされるので使ったほうが楽なBindingと感じます。

Binding にまつわる実装とデバッグの距離

自前コードからの呼び出し口があれば、デバッガを仕掛ける口があり距離としては近づきます。 しかし、Bindingは初期化時に起こるためデバッガとの距離が遠いという、作り上の潜在的な課題があります。

どこまで簡単にデバッグできるかはかなり重要な選択基準になるので順に見てみましょう。

属性 + エントリーポイント

Bindingは属性 + エントリーポイントのエラーのためデバッガを仕掛ける口がFunctionに開いていません。そのため、シンボルをVSで捕まえられるかが容易なデバッグの鍵となりますが、残念ながら多くの場合機能しないことが多いです。

例えば、Microsoft.Azure.WebJobs.Extensions.Storage が 3.0.1のときはシンボルも取れず デバッガはおろかStackTraceも取れません。

Microsoft.Azure.WebJobs.Extensions.Storage が 3.0.1 でTableAttributeBinding でトラブル時は厳しい状況になる

→ Microsoft.Azure.WebJobs.Extensions.Storage 3.0.1から3.0.5に上げることでデバッガがソース内部まで行くように改善されます。(Bindingで落ちることに変わりはない)

この状況は、Bindingのパラメーターを間違えれば容易に再現できるので、エラーから状況を把握することを試してみてください。

リフレクションの多用とRegressionの多さ

WebJobs の Binding はリフレクションを多用しており、些細なミスで機能しないことが起こりえます。

これが顕著なのが、NuGetライブラリバージョンに依存したBinding失敗でバージョンを上げると同一コードで動かないといったRegressionが過去に何度も起こっており、また現在も起こりえます。

例えば、AzureFunctions で利用するプロジェクトが複数あったときに、Microsoft.Azure.WebJobs.Extensions.Storageの下限バージョンとWindowsAzure.Storage バージョンでミスマッチを起こしているとBindingエラーが起こるようになります。(BindingRedirect ではなく、です..... )

この問題は、気づけるなら大したことはありませんが、エントリーポイントのBindingでのエラーのため、自分のパラメーターミスが原因だった時と同じエラーでもあるため気づくのはこんなんです。

結果として、Bindingでのトラブルは解消するのが難しい側面があります。

NuGetバージョンに起因した問題の再現方法

2つプロジェクトを用意し、片方はAzure funcitons のプロジェクトで Microsoft.Azure.WebJobs.Extensions.Storage を入れておきます。(例では3.0.1) もう片方は、WindowsAzure.Storage をNuGetで入れます。(何も考えずにいれると 9.3.3 が入ります)

AzureFunctions で TableBinding をかけて実行すると、即落ちてTableAttributeBindingProvider..cs がないためデバッグがその先のコードにたどり着けません。

TableAttributeBindingProvider..cs がないために落ちた箇所がわかりにくい状況

おもむろに NuGet Package Manager を見ます。最新バージョンの Microsoft.Azure.WebJobs.Extensions.Storage (例は3.0.5) が出ているのでアップグレードします。

Microsoft.Azure.WebJobs.Extensions.Storage を最新版に上げる

コードの変更なく、先程のBinding時のエラーがリフレクションでとってきた tableAttributeのRowKey が null であるためにコケるように変わります。

ライブラリ側のコードで落ちる箇所が表示されるようになる

AzureFunctions プロジェクトの Microsoft.Azure.WebJobs.Extensions.Storage 3.0.5 の依存している WindowsAzure.Storage のバージョンが >= 9.3.1 となっています。

Microsoft.Azure.WebJobs.Extensions.Storageの依存するWindowsAzure.Storageバージョンの確認

依存プロジェクトの WindowsAzure.Storage を 9.3.3 にしていました。

依存プロジェクトのWindowsAzure.Storageに引きずられてアプリの利用バージョンが変わる

そこで依存プロジェクトの WindowsAzure.Storage を 9.3.1 に変更します。

Microsoft.Azure.WebJobs.Extensions.Storage 3.0.5が依存するWindowsAzure.Storageの最小バージョン9.3.1にダウングレードして揃える

AzureFunctions プロジェクトの Microsoft.Azure.WebJobs.Extensions.Storage 3.0.5 が依存している WindowsAzure.Storage も 9.3.1 を見るように変わったことがわかります。

Microsoft.Azure.WebJobs.Extensions.Storage が参照するWindowsAzure.Storageが切り替わったことを確認

コードを何も変更してないけど正常に動くことが確認できます。

問題の回避方法

今、このBinding問題が起こっている方は、多くの場合、適切なバージョンの選択により問題が回避できます。(パラメーターが正しい場合ですが)

Microsoft.Azure.WebJobs.Extensions.Storageの利用しているWindowsAzure.Storage下限バージョンに合わせる

現在 Storage に対するNuGetライブラリは、WindowsAzure.Storage から Microsoft.Azure.Storage.Xxxx への移行途中にあります。特にStorage Tableは影響度が高くしばらくは9.3.1、正しくは Microsoft.Azure.WebJobs.Extensions.Storage の依存しているバージョンに従いましょう。

依存ライブラリに注意する

NuGetバージョン問題は、依存ライブラリになっている場合は個別ライブラリのバージョンに引っ張られる、というNuGet Packageの特性により「自分の気づかない内にバージョンが変わっていた」という状況が起こりやすいようです。

特に、プロジェクト参照していると依存プロジェクトのWindowsAzure.Storageバージョンに引きずられてバージョンが狂ってより死ぬ。という。

Bindingをやめる

そもそもBindingをやめるのが、デバッガビリティ、ライブラリのバージョンに起因したRegressionから簡単に逃げる選択肢になりえます。

つまり、自分でFunction内部でクライアント(CloudTableClientなど) を生成することで、安定した利用ができるでしょう。特にパラメーターが多く問題が起きやすいTableBinding と DB Binding は Binding を行わないのは選択肢となりえます。残念ですが。

複数の方が同じ結論でクライアントを自分で生成されているようです。

Q&A

Table Storage のBindingは空テーブルとか作ってくれて楽なんだけど

Bindingで行われる存在しないTable作成などはTerraformなどの静的な構成管理で担保もできるので、アプリケーションだけでなく全体像でハンドルもありですしコードで担保してもいいでしょう。

Microsoft.Azure.Storage.Xxxx やWindowsAzure.Storage 9.4.xを使い始めるタイミング

Microsoft.Azure.WebJobs.Extensions.Storageに依存しましょう。なお、Storage Tableは 9.4.0 から WindowsAzure.Storage から除外されてCosmosDB ライブラリに移行します。Microsoft.Azure.WebJobs.Extensions.Storageのバージョンが9.4.xを使うまですぐに移行せずに少し様子を見たほうが良さそうです。

https://tech.guitarrapc.com/entry/2019/01/24/041510

BindingでMSIどうなってるの?

Storage Binding はMSIによる認証にも対応していないなど、クラウド側の更新への追随がまだ未整備という側面もあります。

Inboxからのメールクライアント移行

Inbox by Gmail が2019年3月31日で終わりますというアナウンスが流れて半年、ついに4/1です。

じつはまだログインできますがじきにできなくなるでしょう。(4/1 23:00 9 tile からは Inboxが消えました)

メールクライアントをInboxから何に乗り換えたかについてと、その経緯についてメモします。

目次

TL;DR

メールの重要度は下がったもののなくすことは、個人、仕事の両面で不可能なので割り切ります。

自分が欲しい機能を絞ると明確に移行しやすい説があります。

ツールに合わせて欲しい機能を見つめ直し、フローを変えていくのは、知らなかったより良い方法の発見にもなるので今後も基本姿勢にしたいところです。

Webでのメールは使わないフローにしたので、問題になりそうなら課題として解決していくつもり*1

私にとってメールは、「常に受信箱は空に」して、「即時処理(できない場合はスヌーズ)する」、「メールの検索用にタグで分類はするが自動フィルタはかけない」、「不要なメールはそもそも受け取らない、どんどん減らす」のが現状適切に運用の回る方法のようなので、それに適した機能をもったクライアントに移行しました。同じような方にとって参考になれば幸いです。

結論

Edison Mail に移行しました。

mail.edison.tech

アナウンスから半年、Inboxからの移行先を探していたものの見つからず迷っていたましたがアプリに合わせてやり方を修正して移行しています。

変更点は、次のとおりです。

  • メール処理をWeb/スマホの両方で行うのではなく、スマホのみで行うように変更
  • 受信だけはGmailでみることもあるけど、基本的にGmailはアカウントとPCで見る必要があるときだけに割り切り

Inboxで有効に働いた機能

メールを処理するにあたり欲しい機能は、Inbox by Gmail がそのままでした。 もともとOutlook.com で3年ほどメールの確認、振り分けに苦労しており、4桁~メールが常時たまっていました。さらにGmailが加わって過去メールが処理しきれなくなっていい加減捨てるなり継続的に解消できる状況が必要と重い腰を上げました。この状況を改善して、常に未読がない状況を維持できたのはInboxのおかげでした。一晩かけて移行して、その後未読がない状況が維持できたことに感謝して使っていました。

Inboxが有効に機能した理由は次の通りです。

  • 見たときの情報量の少なさ*2
  • メールをフィルターで自動移動させない*3
  • メール通知のスヌーズ機能*4
  • メールの自動的なグルーピング(マネー、ショッピング、トラベル)*5
  • チェックボタン1つでの受信ボックスからの排除*6
  • チェック横からの左クリックのみでタグ移動できる容易さ*7
  • スマホでも同様の操作が提供されている*8

メールの横のチェックボタンやメニューからの移動が最高だった

メールは一日朝と夜の2回しかみたいのですが、メールが来たら次のフローをたどります。

  • プロモーションならまとめて削除。頻度が高すぎるもの、内容によってはメール受信自体を見直し
  • マネーなら入出金確認してチェックぽち
  • Amazonなどで購入したらShoppingに来るのでチェックぽち
  • ほかの自動分類されなかったものはタグをつけてチェックぽち
  • 受信ボックスが何もなくなって終了

見る場所を一か所に限定して、常に受信ボックスがなくなる状態を維持するだけです。

Inboxでのメール表示

Inboxはスマホにおいても同様のUIを提供しており快適度が変わらないのは良かったです。

移行にあたりチェックする機能

Inboxの運用から、今後メールを使っていくにあたり不要な機能がはっきりします。

  • メールのフィルターはしないので不要
  • メールの自動的なグルーピング(マネー、ショッピング、トラベル)はタグで行うため不要
  • チェックボタン1つでの受信ボックスからの排除は、タグ移動で済むので不要
  • クライアントは不要
  • WebもあってるものがなければGmailでテンポラリでみるだけでいいので不要

結果、必要な機能が絞られます。

  • メール通知のスヌーズ機能
  • ワンアクションで既読、タグ移動、削除、スヌーズができる
  • メールアカウントが別れてハンドルできる (まとまってもいいが別れて制御はほしい)

あとは、見た目です。

  • 見たときに情報量が少ない
  • 個別のアカウントにアクセスができる
  • タグ別に確認ができる
  • 動作がスムーズ

残るは動作環境です

  • WebがなければGmail を一時的な閲覧限定で利用
  • Windows/macOS 両方で使うため、クライアントアプリはなし
  • スマホ > Web の利用頻度/優先度のため、Webがないようならスマホアプリをメインに変更 (みるのはiOSのみ)

スマホでは動作のセキュリティと操作を重要視します。

  • FaceId などのセキュリティが組み込まれている
  • バッジ通知が正確(PUSH通知はこなくてもいい)
  • タグ移動で候補がでると最高だがなくてもいい

移行先候補

スマホをメインに移行するかは、Gmail次第です。

Web

  • Gmail : 次節で確認
  • Outlook : そもそも長年Outlook.comを使っておりありえないぐらい使いにくいので全面的やめるように調整済みです

クライアントアプリ

PCでメールのためにアプリをいれたくない、UI/UXが大きく異なるため使いません。

スマホアプリ

  • Gmail
  • Edison Mail
  • Spark
  • Outlook : アプリは比較的悪くないのですがここでは除外 (ヒドイ

移行候補の検証

Gmail

移行先として真っ先にあがるのがGmail です。InboxもGoogleも移行先に推奨しています。

Google 自身がいう、Gmailでの移行は次の内容ですが、ちがうそこじゃない。トップへのピン止めなどせず、さくっと消したいのです。タグでカテゴリわけなどせず見る場所を一か所にとどめてタグは分類置き場にしたいのです。スマートリプライは、相手様が丁寧すぎて使えないケースがあり日本語むずかしい。

support.google.com

Gmailのデフォルト環境では、メールの受信がタブごとに別れます。タブを使った操作は見る場所が複数に散るためかなり好ましくないと判断しました。

Gmail のタブを使った状態

タブをなくしましょう。

メールボックスを一つにした状態

見る場所が一箇所になり良さそうです。

タブをなくしたGmail表示

機能はInboxと同等にあるので、あとは見た目と操作の好みです。順にみてみましょう。

要件 結果
見たときの情報量の少なさ 一メールあたりの送信者、受信本文の量が多くうるさい。カテゴリが折りたたまれておりアクセスが煩雑
メール通知のスヌーズ機能 あります。メールをホバーすると表示とスヌーズが表示
ワンアクションで既読、タグ移動、削除、スヌーズができる アーカイブを使うことで受信ボックスから消えます。削除もあります。しかしタグ移動ができません

スヌーズはok です。

Gmailのスヌーズ

受信ボックスからの排除もアーカイブでokです。

アーカイブ

問題となるのが、タグ移動とメールの自動的なグルーピングです。 Gmail でグループしようと思った場合にやるのが、前述の受信ボックスのタブ、あるいはフィルターでタグ移動ですが両方使いません。 受信ボックスのメールがグループ化されればInboxにほぼ近くよかったのですが、タブで見る場所が分かれるのはナシなのであきらめましょう。

フィルタをしない

手動でタグ移動は別にいいのでやろうかと思いましたが、右クリックから移動はたった1つ動作が増えるだけですがナシです。全メールでやってられないです。

タグの移動は右クリックになる

やりたいことが明確になりましたが、Gmail ではInbox ほどフローの省力化はできないようです。

受信ボックスを限定して、フィルタなどを使わず見る必要のあるメールだけを受け取り、素早く分類して、メールボックスを常に空にする。

見た目がうるさいのはあきらめましょう。

この時点で、Webでのメール処理は現時点でいい方法がないと判断し、割り切ってスマホでの完結を目指します。

Gmail iOSアプリ

スマホアプリはかなり残念です。

スワイプでアーカイブは提供していますが、スヌーズやタグ移動などは個別でメールを開いて処理する必要があります。

スマホではワンアクションで必要な機能をスムーズに呼び出したいため、メールのスワイプ動作をカスタム設定できれば楽なのにという感じはあります。

アカウントが分かれるのは別にいいのですが、統合されたメールボックスの利便性はスマホアプリではWeb以上に感じます。

この時点でGmailのスマホアプリはなしです。

Edison Mail

mail.edison.tech

一通り機能が網羅されています。動作が軽快で、TouchIdがあり通知が正確なのが良さです。

残りは、タグ移動時のスマートな提案がほしいところです。

スワイプの動作がカスタマイズできるため、これらはすべてワンアクションで実行できます。

メール通知のスヌーズ機能 ワンアクションで既読、タグ移動、削除、スヌーズができる

Edison mail でスワイプ操作のカスタマイズ

また、メールアカウントを複数登録したときに、分けて表示もできますが統合メールボックスでまとめてみることもできます。

メールアカウントが別れてハンドルできる (まとまってもいいが別れて制御はほしい)

個別のアカウントにアクセスができる

メールのプレビュー行数を1行にできるので見た目もシンプルです。

見たときに情報量が少ない

メールアカウントを選択するとタグ別に確認もできます。

タグ別に確認ができる

動作もキビキビしており快適です。

動作がスムーズ

FaceId にデフォルトで対応しており嬉しさがあります。

FaceId などのセキュリティが組み込まれている

起動時にFaceId認証

バッジ通知が正確で、新規通知時にもバッジのカウントがずれないので良さがあります。

バッジ通知が正確(PUSH通知はこなくてもいい)

タグ移動時に移動候補はでないので、そこは残念です。

タグ移動で候補がでると最高だがなくてもいい

メールの青ポッチをスワイプで既読にできるのも楽です。

Spark

sparkmailapp.com

こちらも一通り機能が網羅されています。動作が軽快で、スマートな通知があり通知が正確なのが良さです。統合メールボックスもアカウントが適切に出て良さ味があります。

残りは、TouchIdがあれば嬉しいのと、アプリの動きが遅いのが気になります。

スワイプの動作がカスタマイズできるため、これらはすべてワンアクションで実行できます。

メール通知のスヌーズ機能

ワンアクションで既読、タグ移動、削除、スヌーズができる

Sparkのスワイプ設定

また、メールアカウントを複数登録したときに、分けて表示もできますが統合メールボックスでまとめてみることもできます。 この統合メールボックスが、スマートアカウントというだけあってメールアカウントがチラっとグループされていたりしてEdison Mailより便利です。

メールアカウントが別れてハンドルできる (まとまってもいいが別れて制御はほしい)

個別のアカウントにアクセスができる

メールのプレビュー行数は1行で見た目もシンプルです。

見たときに情報量が少ない

メールアカウントを選択するとタグ別に確認もできます。

タグ別に確認ができる

動作は少し重く、時々起動しても真っ白だったりするのは難点です。

動作がスムーズ

パスキーロックには対応していますが、FaceIdには対応していません。パスキーロック嫌いなので残念です。

FaceId などのセキュリティが組み込まれている

バッジ通知はは、新規通知時にもバッジのカウントがずれたりします、既読してもバッジが残ったりするので残念です。

バッジ通知が正確(PUSH通知はこなくてもいい)

タグ移動時に候補はでるのはかなり嬉しいです。便利。

タグ移動で候補がでると最高だがなくてもいい

他に、メールの委任などチームで共有してメール編集できるのが便利です。

Edison か Sparkか

チーム機能を使わないので、機能的にはほとんど変わりません。

このため、動きの機敏さとFaceId とバッジの正確さで Edison Mail を選択しました。

とはいえ、まだ移行完了して2週間なので Edison Mail と Spark の療法をインストールして試しています。

Webは予定どおりメールを見ることがなくなり、何かしらのアカウント作成時のVerify などに限定されていて良い感じです。

移行していて思いましたが、メールに求めることがみんな違うのでいろんな代替案があるのは最高なので、多様な選択肢があることに感謝です。

移行後のフローは、自動振り分けがないので順にやるだけです。この意味でInboxかSpark並みの振り分けがほしい

  • プロモーション系は即削除。頻度が高すぎるもの、内容によってはメール受信自体を見直し
  • マネーなら入出金確認してタグ移動
  • Amazonなどで購入したらタグ移動
  • ほかの自動分類されなかったものはタグ移動
  • 受信ボックスが何もなくなって終了

Edison への移行

Inbox からの移行で困ったのが、Shopping や Money といった自動振り分けはタグではなかったことです。つまり他のメールクライアントでは認識できず受信メールにまとめて表示してしまいます。

せめてInboxからまとめてタグ付けできればよかったのですが、Inboxでメールを複数まとめて振り分け操作はできません。 Gmailを見ても内部的に受信メールを振り分けてくれただけです。

このため、移行後のタグをShopping、Financeと作り、Inboxでちくちく移動させました。しかしGmailで受信メールのタグをが残ったのであとはぺちぺち「タグ移動」。

これで移行完了です。さしみたんぽぽ。

*1:今の所問題なさそう。

*2:メールを見たときの大量の情報はストレスとなりやる気を削ぎます

*3:見る場所を一か所に絞ることで見る場所を探すストレスを下げます

*4:今見たくない、でも後からだと忘れそう、というストレスを解消します

*5:特に無駄なプロモーションをまとめて消せる

*6:操作一つにストレスを受けたくない

*7:移動めんどくさいのストレスを減らす

*8:環境問わず同じ操作ができるストレスのなさ