【Kubernetes】--dry-runでmanifestファイルの雛形を手に入れる

CKAD対策で学んだtips。
Kubernetes上のリソースを作る際、複雑な設定の場合はmanifestファイルを書かなければいけない。しかしYAML形式で記述を行うのはとても時間がかかるうえ、それぞれのリソース種別ごとの記法を全部は覚えられない。公式ドキュメントからコピペしてくることもできるが、それも探すのが面倒なうえタイムロスになる。
そこで、manifestファイルの雛形を手に入れたいときは --dry-run が役立つ。

--dry-run はkubectlコマンドのオプションで、 applycreate を実行せず、クライアントサイドでできるバリデーションを行ってくれる。この出力をYAML形式でファイルに書き出せば、それがmanifestファイルの雛形になる。

$ kubectl run nginx --image=nginx --dry-run=client -o yaml > nginx.yaml
$ cat nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      run: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

これをもとに各項目の値を変えることができる。ゼロからmanifestファイルを作るよりはるかに楽。
なお、設定すべき項目のうちコマンド上で設定できるオプションをあらかじめ設定してファイルに出力すると、作業がより効率的になる。

【GitHub Actions】ファイルパスのワイルドカード指定には「*」では不十分

先日いつものようにプルリクエストを作ったら、GitHub Actionsのworkflowが全く動いてくれなかった。GitHub ActionsのSpending limitやGitHub自体の障害も疑ったが、原因はもっと単純なものだった。

yamlファイルでは、 on 節を以下のように記述していた。

on:
  pull_request:
    branches:
      - master
    paths:
      - '*'
      - '!README.md'
      - '!.gitignore'
      - '!.github/workflows/dev-workflow.yaml'
      - '!.github/workflows/release-workflow.yaml'
...

これでコードのファイルは全て検知すると思っていたが、 * を指定していたのが問題だった。GitHub Actionsでは、 * ではスラッシュにマッチしないため、これではトップディレクトリにあるファイルしか検知してくれない。

docs.github.com

workflowが動かなかったプルリクエストでは、 src/main.py のみに差分があったため、上記の条件では検知されなかった。それ以前のプルリクエストでは、READMEやworkflowファイルを合わせて更新していたので、たまたま問題なかっただけだった。

サブディレクトリ以下にあるファイルにもマッチさせたい場合、 ** と指定すればよい。

on:
  pull_request:
    branches:
      - master
    paths:
      - '**'
      - '!README.md'
      - '!.gitignore'
      - '!.github/workflows/dev-workflow.yaml'
      - '!.github/workflows/release-workflow.yaml'
...

ただ、このように「少数のファイル以外全てにマッチさせたい」という場合、 paths-ignore を使う方がより単純に書ける。

docs.github.com

on:
  pull_request:
    branches:
      - master
    paths-ignore:
      - 'README.md'
      - '.gitignore'
      - '.github/workflows/dev-workflow.yaml'
      - '.github/workflows/release-workflow.yaml'
...

【GitHub Actions】docker-build-pushでECRにimageをpushする

GitHub Actionsでworkflowを書くとき、imageをビルドしてECRにpushする処理を書く機会が多い。今までは以下の様に書いていた。

jobs:
  build:
    name: Build and push Docker image
    runs-on: ubuntu-latest
    env:
      IMAGE_NAME: ${{ secrets.CONTAINER_REGISTRY_PATH }}/path/to/ecr/repo
      IMAGE_TAG: ${GITHUB_SHA}
    steps:
    - uses: actions/checkout@master
    - name: Login to ECR
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      run: $(aws ecr get-login --no-include-email)
    - name: Build image
      run: docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
    - name: Push image
      run: docker push ${IMAGE_NAME}:${IMAGE_TAG}

しかし先日、良さそうなライブラリを教えてもらった。

github.com

Docker Imageのビルドからレポジトリへのログイン・pushまでを行ってくれるライブラリで、push先もDockerHub, GCR, ECR, GitHub Docker Registryに対応している。
ECRにpushする場合は以下のように書ける。

jobs:
  build:
    name: Build and push Docker image
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - uses: mr-smithers-excellent/docker-build-push@v4
      name: Build & push Docker image
      with:
        image: path/to/ecr/repo
        tag: ${GITHUB_SHA}
        registry: ${{ secrets.CONTAINER_REGISTRY_PATH }}
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

docker image関連のステップ数を大幅に減らすことができ、workflowが見やすくなる。

念のためコードも確認してみる。

docker-build-push/docker.js at master · mr-smithers-excellent/docker-build-push · GitHub

docker関連の処理はdocker.jsに記述されている。ECRの場合 aws ecr get-login でログインを行い、その後 docker build , docker push を行うというよくある処理になっている。

【Kubernetes】Jobの結果をコマンドで取得する

KubernetesのJobのうち、backOffLimit を超えてしまい失敗したもののみを抽出して削除したいときがあった。Jobの結果は type フィールドに入っており、値は CompleteFailed のいずれか。また、Jobが実行中の場合 type フィールド自体が存在しない。

kubectl get job <job_name> -o json
{
    "apiVersion": "batch/v1",
    "kind": "Job",
    ...
    "status": {
        "completionTime": "2020-09-27T03:20:54Z",
        "conditions": [
            {
                "lastProbeTime": "2020-09-27T03:20:54Z",
                "lastTransitionTime": "2020-09-27T03:20:54Z",
                "status": "True", 
                "type": "Complete" # Job結果
            }
        ],
        "startTime": "2020-09-27T03:20:17Z",
        "succeeded": 1
    }
}

このJob結果をコマンドで取りたいと思い、jsonpathでがんばってみた。

kubectl get job $job_name -n $NAMESPACE -o jsonpath='{.status.conditions[].type}'

しかし、これだとtypeフィールドがない場合に対応できない。調べてみたが、jsonpathでは「フィールドがない場合は空の値を返す」という挙動はできないようだ。
最終的にjqコマンドに切り替えて、以下のように実現した。 []? を指定することで、そのフィールドが存在しない場合はnullが返ってくる。

kubectl get job pi -o json | jq '.status.conditions[]?.type' 

Kubectl Cheat Sheetには、jqの方がjsonpathよりも複雑なことができると書いてある。普段使いの場合はjqの方が良いのかもしれない。

jamesdefabia.github.io

【AWS】iam:PassRoleとsts:AssumeRoleの違い

AWSでロールを扱う際、iam:PassRolests:AssumeRole の両方が説明に出てくることが多い。どちらもロールを使えるようにするために必要なアクションだが、いまいち違いがわからなかったので改めて調べてみた。

iam:PassRole

まず iam:PassRole は、ユーザに対して付与するアクセス許可ポリシーで指定できるアクションである。
例えばAWS Batchを使うとき、ジョブの内容に応じたアクセス許可ポリシーをロールに付与しておき、そのロールをAWS Batchで指定しておく必要がある。ジョブが作成されると、AWS Batchがロールを使ってジョブの内容を実行する。
このとき、もしユーザ自身が aws batch submit-job コマンド等でジョブを登録する場合、 iam:PassRole アクション内で当該ロールをResourceに指定したポリシーを作り、それをユーザにアタッチする必要がある。これを行わないと、ロールをサービスに渡すことができない。 AWS Batchの公式ドキュメントにある例では、iam:PassRole アクションはアクセス許可ポリシー内で以下のように記述されている。

docs.aws.amazon.com

{
    "Effect": "Allow",
    "Action": [
        "iam:PassRole"
    ],
    "Resource": [
        "arn:aws:iam::<aws_account_id>:role/TestRole"
    ],
    "Condition": {
        "StringEquals": {
            "iam:PassedToService": [
                "batch.amazonaws.com"
            ]
        }
    }
}

このポリシーをアタッチされたユーザは、 Resource で指定されたロールをアタッチできるようになる。加えて、 IAM条件キー iam:PassedToService を使うことで、アタッチの対象にできるサービスがAWS Batchのみに制限されている。

docs.aws.amazon.com

(補足だが、ロールをアタッチできる対象はAWSサービスのみではない。他のAWSアカウントのユーザにロールを付与して、特定のアクションをクロスアカウントで行ってもらうという使い方もある)

個人開発をしている場合、 iam:PassRole なんて指定した覚えがないという人も多いかもしれない。個人用環境でAWSを触るときによく使われる AdministratorAccess ポリシーでは、全てのアクションが全てのリソースに対して許可されているので、 iam:PassRole を意識する必要はなくなっている。また、各サービスごとにあらかじめ用意されているアクセス許可ポリシーを使っている場合も、既に iam:PassRole が許可されていることが多い。 AWS Batchであれば、 AWSBatchFullAccess ポリシー内で、Batch関連のあらかじめ用意されたロールについて iam:PassRole が許可されている。

docs.aws.amazon.com

sts:AssumeRole

sts:AssumeRole は、ロールに対して付与する信頼ポリシーの中で指定できるアクションである。この「信頼ポリシー」は iam:PassRole に登場した「アクセス許可ポリシー」とは概念が異なる。アクセス許可ポリシーがアイデンティティベースのポリシーなのに対し、信頼ポリシーはリソースベースのポリシーの一種である。アイデンティティベースのポリシーにはリソースネーム(ARN)が付与され、様々なアイデンティティに使い回してアタッチすることが可能だが、リソースベースは特定のリソースと1対1・不可分の関係である。

docs.aws.amazon.com

sts.AssumeRole は、そのロールをどのリソースにAssume(引き受けさせる)できるかを定義する。もし Assume を管理するという概念がなかった場合、例えばAWSサービスAのために強い権限を持つロールを作ったとして、そのロールを別のAWSサービスBに与えることができてしまう。その場合、サービスBが意図しない強い権限を持ち、セキュリティ上の問題となりうる。そこで、そのロールをどのリソースに渡すことができるか、ロール自体と不可分のインラインポリシーとして定義できるようになっている。もちろん sts.AssumeRole を何も定義しない場合、そのロールはどのリソースにも渡せない。
iam:PassRole の場合と同様に、AWSによってあらかじめ用意されているロールには、既に sts:AssumeRole が定義されている。例えば AWSBatchServiceRole には以下の信頼ポリシーが付与されており、もしこのポリシーがない場合は改めて作成してからロールを使うよう案内されている。

docs.aws.amazon.com

{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {"Service": "batch.amazonaws.com"},
        "Action": "sts:AssumeRole"
    }]
}

この信頼ポリシーにより、 AWSBatchServiceRoleAWS Batch以外のAWSサービスには引き受けられないようになっている。
なお、 sts:AssumeRole のPrincipalはAWSサービスのみではなく、他のAWSアカウントのユーザなどにもできる。

iam:PassRolests.AssumeRole により、「ユーザはどのロールを付与することができるか」「そのロールはどのリソースに付与されることができるか」の両面からロールを管理できるようになっている。ロールは通常であれば持っていない権限を一時的に付与するものであるため、強い権限が意図しない相手に渡らないようにするための仕組みが必要だったのだろう。

ロールを作って使うときの全体像は、以下のドキュメントがわかりやすい。
docs.aws.amazon.com

【AWS】CloudTrailログからリクエストのGlobal Condition Keyを推測する

AWSのポリシーを作るとき、 Condition 句を使って特定のプリンシパルをポリシー適用の例外として指定したい時がある。 Condition 句の中で指定した条件は、リクエストに含まれるGlobal Condition Keyと照らし合わせて確認され、リクエストが許可されるかどうかが決まる。

docs.aws.amazon.com

しかし、業務で特定のアカウントやVPC・サービスのみがS3バケットにアクセスできるようバケットポリシーを作ろうとした際、テストの方法がわからず、ポリシーを変えてはリクエストを送ってみて確認、という作業を繰り返すことになってしまった。
より効率的な方法はないかカスタマーサポートに聞いてみたところ、Global Condition Keyを直接確認できる方法は存在しないが、CloudTrailでリクエストログを取得していればそこから推測できると教えてもらった。

CloudTrailのログのフィールドは以下のドキュメントにまとまっている。

docs.aws.amazon.com

例えば、AWS glueサービスからS3バケットに置いてあるスクリプトをダウンロードしようとして、Access Deniedのエラーが出たとする。その場合、エラーメッセージに requestId が表示されていれば、それを使ってCloudTrailのログを検索することで、当該リクエストの詳細なログを取得できる。
ポリシーの Condition によく使うキーとの対応として、 aws:userid は CloudTrailログの useridentity.principalId に、 aws:PrincipalAccountuserIdentity.accountId に相当する。また、VPC Endpoint経由でのアクセスかどうか確認するには、CloudTrailログ内に vpcEndpointId フィールドが存在するかを確認すればよい。
リクエストのログを取得することができれば、トライアンドエラーでポリシーを何度も変更する必要がなくなり、ポリシー設計が非常に楽になる。

【Terraform】AWS providerの認証で詰まった

先日リモート環境から、terraformで管理しているAWSのresourceを更新する作業を始めようとして terraform init したところ、アクセスができずに詰まってしまった。

$ terraform init
Initializing modules...

Initializing the backend...
Error refreshing state: AccessDenied: Access Denied
    status code: 403, request id: XXXXXXXXXXXXXXXX, host id: xxxxxxxxxxxxxxxxxxxxxxxxxxxx

リモート環境でもawsに接続できるよう ~.aws/credentials ファイルにaccess keyとsecretを入れておいたのだが、Terraformのドキュメントを見たところ、 ~.aws/credentials を作るだけでは認証ができないことがわかった。

registry.terraform.io

shared credentials fileで認証しようと思う場合、 provider resourceの shared_credential_file にファイルパスを渡しておかなければいけない。よく考えればTerraformで使えるbackendはAWSだけではないので、shared credentials fileを単に置いておいただけでデフォルトで読み取ってくれることはない。

今回はproviderの設定を変えることができない状況だったが、Terraformの認証では環境変数があればshared credentials fileよりも高い優先度で認証に使われる。 AWS_ACCESS_KEY_ID , AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION環境変数を正しく設定すると terraform init を行うことができた。