GeekFactory

int128.hatenablog.com

決済手段を選択するビジネスルールを考える

お店やネットで買い物する時のビジネスルールが複雑になってきたので書き出してみました。

以下の順に評価して条件を満たす決済手段で支払います。

  1. USD/EUR建ての場合:Sony Bank WALLET
  2. ANA FESTAの場合:ソラチカカードVisa(5%割引)
  3. ビックカメラの場合:ビックカメラSuicaカード(10%)
  4. アトレやJR定期券の場合:JREカード(5%)
  5. 投資信託の場合:楽天カード(1%)→楽天証券
  6. コード決済が使える場合:Origami Pay(1%割引)→Kyash Visa(1%)→EPOS(1.5%)
  7. コード決済が使える場合:楽天Pay(1%)→Kyash Visa(1%)→EPOS(1.5%)
  8. Visa決済が使える場合:Kyash Visa(1%)→EPOS(1.5%)
  9. プリペイドVisaがダメな場合:EPOS (1.5%)
  10. 東京メトロ乗車の場合:ソラチカPASMO
  11. Suicaが使える場合:SuicaJREカードオートチャージ (3%)
  12. PayPayが使える場合:PayPay (0.5%)
  13. 現金

ただし、以下の例外条件があります。

  • コード決済のキャンペーン期間中は還元率の高い決済手段を優先的に選択します。例えば、2/4時点では一部店舗でPayPay(40%)があります。
  • Kyash Visaカードのポイント付与対象外(航空券や鉄道など)の場合はEPOSカード(or 楽天カード)で支払います。
  • Kyash Visaカードの月間支払いが12万円を超えた場合はEPOSカード(or 楽天カード)で支払います。
  • EPOSカードの年間支払いが100万円を超えた場合は楽天カードで支払います。

また、支払い前に以下を考慮します。

  • 決済とは別にdポイントを付与できる場合があります。
  • 西友ネットスーパーやApple Storeなどの場合はRebatesを経由してから発注します。
  • ポイントサイトのキャンペーンがある場合があります。(最近あまり見ていない)

上記はFeliCa/NFC決済を考慮していません。FeliCa/NFC決済が使える場合はビジネスルールが変わってくると思います。

もし、幅広い決済手段に対応したプロキシがあったとしても、これらのビジネスルールを実装してテストするのは大変そうです。

kindでクラスタが起動しない原因を調べる

kind create cluster コマンドでKubernetesクラスタが起動しない場合、以下のようなメッセージが表示されます。

 ✗ Starting control-plane 🕹️
ERROR: failed to create cluster: failed to init node with kubeadm: command "docker exec --privileged kind-control-plane kubeadm init --ignore-preflight-errors=all --config=/kind/kubeadm.conf --skip-token-print --v=6" failed with error: exit status 1

クラスタが起動しない原因を調査するには以下の方法があります。

  • kindコマンドのログレベルを上げる
  • kindノードコンテナの内部に入って、Control Planeのログを調査する

kindコマンドのログ

kindコマンドに -v オプションを渡すと詳細なログが表示されるようになります。例えば -v10 を渡すと以下のようなログが表示されます。

Creating cluster "kubelogin-acceptance-test" ...
DEBUG: docker/images.go:70] Pulling image: kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62 ...
 ✓ Ensuring node image (kindest/node:v1.17.0) 🖼
 ✓ Preparing nodes 📦
DEBUG: config/config.go:90] Using kubeadm config:
apiVersion: kubeadm.k8s.io/v1beta2
clusterName: kind
controlPlaneEndpoint: 172.17.0.3:6443
controllerManager:
  extraArgs:
    enable-hostpath-provisioner: "true"
kind: ClusterConfiguration
kubernetesVersion: v1.17.0
(以下略)

kindコマンドのログからは、kubeadmに渡す設定ファイルの内容、Control Planeで使うイメージ、Control Planeの各設定ファイルのパスなどが分かります。kindコンテナの内部に入って調査する時の材料になります。

kindノードコンテナの内部にあるログ

kindではノードコンテナの内部でKubernetesクラスタが動いています。ノードコンテナのIDや名前は以下のようにdocker psコマンドで確認できます。

% docker ps
CONTAINER ID        IMAGE                        COMMAND                  CREATED              STATUS              PORTS                       NAMES
38ea3c1f766a        kindest/node:v1.17.0         "/usr/local/bin/entr…"   About a minute ago   Up About a minute   127.0.0.1:32768->6443/tcp   kind-control-plane

kindノードコンテナの内部ではsystemdが動いています。systemdのログは以下のようにdocker logsコマンドで確認できます。

% docker logs kind-control-plane
INFO: ensuring we can execute /bin/mount even with userns-remap
INFO: remounting /sys read-only
(中略)

Welcome to Ubuntu 19.10!

[  OK  ] Started Dispatch Password …ts to Console Directory Watch.
[  OK  ] Listening on Journal Socket.
         Mounting FUSE Control File System...
         Starting Remount Root and Kernel File Systems...

(以下は抜粋)
         Starting containerd container runtime...
[  OK  ] Started kubelet: The Kubernetes Node Agent.
[  OK  ] Started containerd container runtime.

このように、ノードコンテナの起動時にsystemdがcontainerdやkubeletを実行して、Control Planeの各コンポーネントが実行される流れになっています。

コンポーネントのログを確認するには、以下のようにノードコンテナの内部に入る必要があります。

% docker exec -it kind-control-plane /bin/bash

kindノードコンテナの内部では以下のプロセスが動いています。

  • apiserver(コンテナとして動作)
  • kube-controller-manager(コンテナとして動作)
  • kube-scheduler(コンテナとして動作)
  • etcd(コンテナとして動作)
  • kubelet(プロセスとして動作)

これらが立ち上がると以下のコンテナも動き始めます。

  • kube-proxy
  • coredns
  • kindnet
  • local-path-provisioner

各コンテナのマニフェスト/etc/kubernetes/manifests/ にあります。

# ls -la /etc/kubernetes/manifests/
total 24
drwxr-xr-x 1 root root 4096 Jan 24 11:53 .
drwxr-xr-x 1 root root 4096 Jan 24 11:53 ..
-rw------- 1 root root 1805 Jan 24 11:53 etcd.yaml
-rw------- 1 root root 3204 Jan 24 11:53 kube-apiserver.yaml
-rw------- 1 root root 3090 Jan 24 11:53 kube-controller-manager.yaml
-rw------- 1 root root 1120 Jan 24 11:53 kube-scheduler.yaml

また、各コンテナのログは /var/log/containers/ にあります。

# ls -la /var/log/containers/
total 28
drwxr-xr-x 2 root root 4096 Jan 24 12:45 .
drwxr-xr-x 4 root root 4096 Jan 24 11:53 ..
lrwxrwxrwx 1 root root  114 Jan 24 11:53 etcd-...
lrwxrwxrwx 1 root root  136 Jan 24 12:45 kube-apiserver-...
lrwxrwxrwx 1 root root  152 Jan 24 11:53 kube-controller-manager-...
lrwxrwxrwx 1 root root  134 Jan 24 11:53 kube-scheduler-...

kubeletのログはどこに出力されるか分かりませんでした。詳しい人教えてください。 kubeletのログは下記のコマンドで確認できます。

# journalctl -u kubelet
Jan 27 01:25:58 kubelogin-acceptance-test-control-plane systemd[1]: Started kubelet: The Kubernetes Node Agent.
(以下略)

これらの設定ファイルやログを参照すると、クラスタが起動しない原因が分かると思います。例えば、apiserverの引数が間違っている場合はapiserverコンテナに以下のログが出ます。

# cat /var/log/containers/kube-apiserver-...
2020-01-24T11:56:18.6797274Z stderr F I0124 11:56:18.679446       1 server.go:596] external host was not specified, using 172.17.0.3
2020-01-24T11:56:18.6807181Z stderr F I0124 11:56:18.680394       1 server.go:150] Version: v1.17.0
2020-01-24T11:56:18.9807349Z stderr F Error: invalid authentication config: 'oidc-issuer-url' ("http://localhost") has invalid scheme ("http"), require 'https'
2020-01-24T11:56:18.9827157Z stderr F Usage:
2020-01-24T11:56:18.9827978Z stderr F   kube-apiserver [flags]
2020-01-24T11:56:18.9828244Z stderr F
2020-01-24T11:56:18.9828494Z stderr F Generic flags:
2020-01-24T11:56:18.9828729Z stderr F
(以下略)

kindにはログを一括エクスポートする機能があります。詳しくは https://kind.sigs.k8s.io/docs/user/quick-start/#exporting-cluster-logs を参照してください。

See Also

GitHub GraphQLで新しいPull Requestを作成する

GitHub GraphQLで新しいPull Requestを作成するにはcreatePullRequest mutationを利用します。RESTの場合と同様に、以下のようにheadとbaseを指定します。

  • head ref: 適用したい変更が含まれるブランチ。cross repositoryの場合はforkされたリポジトリ
  • base ref: 変更を適用するブランチ。cross repositoryの場合はfork元のリポジトリ。一般にはデフォルトブランチ。

本稿の内容はすべてGraphQL API Explorerで再現可能です。

Pull Requestの作成

ここでは以下のPull Requestを作成します。

まず、head repositoryのIDを取得します。

query {
  repository(owner: "octocat", name: "Spoon-Knife") {
    id
  }
}
{
  "data": {
    "repository": {
      "id": "MDEwOlJlcG9zaXRvcnkxMzAwMTky"
    }
  }
}

createPullRequest mutationを実行します。以下の引数が必須です。

repositoryId (ID!) The Node ID of the repository.

baseRefName (String!) The name of the branch you want your changes pulled into. This should be an existing branch on the current repository. You cannot update the base branch on a pull request to point to another repository.

headRefName (String!) The name of the branch where your changes are implemented. For cross-repository pull requests in the same network, namespace head_ref_name with a user like this: username:branch.

title (String!) The title of the pull request.

https://developer.github.com/v4/input_object/createpullrequestinput/

repositoryIdにはhead repositoryのIDを指定します。また、cross repositoryの場合はheadRefNameにユーザ名のプレフィックスを付けます。以下の例を見てください。

mutation {
  createPullRequest(input: {
    repositoryId: "MDEwOlJlcG9zaXRvcnkxMzAwMTky",
    baseRefName: "master",
    headRefName: "int128:patch-1",
    title: "Example",
    body: "Using the GraphQL mutation."
  }) {
    pullRequest {
      id
    }
  }
}
{
  "data": {
    "createPullRequest": {
      "pullRequest": {
        "id": "MDExOlB1bGxSZXF1ZXN0MzY1MDUyNTMy"
      }
    }
  }
}

これで下記のPull Requestが作成されました。

github.com

Pull Requestの検索

前項で作成したPull Requestを検索します。

  1. head refに紐づくPull Requestを検索する。
  2. base repositoryに存在するPull Requestを検索する。

まず、head refに紐づくPull Requestを検索してみましょう。以下のようにassociatedPullRequests connectionを利用します。

{
  repository(owner: "int128", name: "Spoon-Knife") {
    ref(qualifiedName: "refs/heads/patch-1") {
      associatedPullRequests(baseRefName: "master", first: 1) {
        nodes {
          id
          title
        }
      }
    }
  }
}
{
  "data": {
    "repository": {
      "ref": {
        "associatedPullRequests": {
          "nodes": [
            {
              "id": "MDExOlB1bGxSZXF1ZXN0MzY1MDUyNTMy",
              "title": "Example"
            }
          ]
        }
      }
    }
  }
}

head refから検索する方法はhead refが存在する場合にのみ使えます。Pull Requestをマージ(or クローズ)した後にブランチを削除した場合は、head refが存在しないので検索できなくなります。

次は、base repositoryに存在するPull Requestを検索してみましょう。

{
  repository(owner: "octocat", name: "Spoon-Knife") {
    pullRequests(baseRefName: "master", headRefName: "patch-1", first: 1) {
      totalCount
      nodes {
        id
        title
        url
      }
    }
  }
}
{
  "data": {
    "repository": {
      "pullRequests": {
        "totalCount": 620,
        "nodes": [
          {
            "id": "MDExOlB1bGxSZXF1ZXN0MTU5NzIz",
            "title": "Edited index.html via GitHub",
            "url": "https://github.com/octocat/Spoon-Knife/pull/21"
          }
        ]
      }
    }
  }
}

まったく関係のないPull Requestが出てきてしまいました。この方法では patch-1 というhead refのPull Requestがすべて表示されてしまいます。pullRequests connectionでは検索条件にhead repositoryを入れられないため、フォークされたリポジトリがすべて検索対象になる問題があります。

今のところ、head refから検索する方が確実にPull Requestを絞り込めるのでよさそうです。