Kubernetes: Admission Controlとは何か

KubernetesのAdmission Controlについて理解のためメモをまとめてみました。Kubernetes 1.2時点での情報です。

## Admission Controlとは

Admission ControlとはKubernetesのAPI Serverのリクエスト制御の機能です。APIリクエストに対して認証、認可を行ったあとのフェーズで、別途そのリクエストを受け入れるか制御を行います。また場合によって、リクエストの変更や別の操作を行います。

プラグイン形式で複数の制御の方法が用意されており、APIの起動オプション``--admission-control``で有効にしたいものをカンマ区切りで指定します。

## プラグイン

APIサーバーのデフォルトでは`AlwaysAdmit`のみが有効になっています。(参考:  [cmd/kube-apiserver/app/options/options.go](https://github.com/kubernetes/kubernetes/blob/62eb7d3db8b0d968bd3346d067bb8f58e086352b/cmd/kube-apiserver/app/options/options.go#L100)

[マニュアル](http://kubernetes.io/docs/admin/admission-controllers/
)では下記の指定が推奨されています。

```
--admission-control=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
```

プラグインはチェーン式にいずれかのプラグインがdenyするまで呼ばれていくため、順序に意味があるので注意が必要です。(変更を伴うプラグインもあるため)

## プラグイン一覧

deprecatedなプラグインは省略しています。推奨設定に入っているものは :+1: を付けています。

### `AlwaysAdmit`

すべてのリクエストを許可します。APIサーバーの``--admission-control``に何も指定しない場合、これが設定されています。

- ソース: [plugin/pkg/admission/admit/admission.go](https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/admit/admission.go)

### `AlwaysPullImages`

Podの`imagePullPolicy`を`Always`に強制的に変更します。マルチテナントでKubernetesを利用する際これを使わないと、クレデンシャルが必要なPrivateのイメージであっても、Nodeがpull済みのものはクレデンシャル無しで別のPodが使用することができてしまいます。

- ソース: [plugin/pkg/admission/alwayspullimages/admission.go](https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/alwayspullimages/admission.go)

### `AlwaysDeny`

テスト用途ですべてのリクエストを禁止します。

- ソース: [plugin/pkg/admission/deny/admission.go](https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/deny/admission.go)

### `DenyEscalatingExec`

privilegeやHostIPC, HostPIDを要求するPodに対して`exec`や`attach`を禁止します。クラスタがprivilegeをサポートサポートする場合、この設定が強く推奨されます。

- ソース: [plugin/pkg/admission/exec/admission.go](https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/exec/admission.go)

### `ServiceAccount` :+1:

[ServiceAccount](http://kubernetes.io/docs/user-guide/service-accounts/)というPodのためのアカウント(デフォルトでは`default`が使われる)を制御するための仕組みです。PodはAPIにアクセスするためのServiceAccountのトークンが`/var/run/secrets/kubernetes.io/serviceaccount`に自動でマウントされますが、このプラグインがそのマウントをPodに追加することで実現しているようです。

- ソース: [plugin/pkg/admission/serviceaccount/admission.go](https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/serviceaccount/admission.go)

### `SecurityContextDeny` :+1:

[SecurityContext](http://kubernetes.io/docs/user-guide/security-context/)の設定のうち、`SELinuxOptions`、`RunAsUser`などの設定をしているものを禁止します。(セキュリティ上の理由？)

- ソース: [plugin/pkg/admission/securitycontext/scdeny/admission.go](https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/securitycontext/scdeny/admission.go)

### `ResourceQuota` :+1:

リクエストがnamespace内のリソース制限(`ResourceQuota`)を超えていないかチェックします。`ResourceQuota`を使う場合、このプラグインを有効にしなければなりません。

このプラグインは内部的に`ResourceQuota`の使用量をアップデートする処理があるため、プラグインの最後尾に指定することが強く推奨されます。(前に指定すると、後のプラグインでdenyされた場合に使用量がおかしくなってしまう)

- ソース: [plugin/pkg/admission/resourcequota/admission.go](https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/resourcequota/admission.go)

### `LimitRanger` :+1:

namespaceの[LimitRange](http://kubernetes.io/docs/admin/limitrange/)で指定されたPodの制限を行います。`ResourceQuota`と違い、`LimitRange`ではPod単体の制限を行うもので、`resources`で指定するCPU, Memoryの範囲を制限することができます。

- ソース: [plugin/pkg/admission/limitranger/admission.go](https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/limitranger/admission.go)

### `InitialResources` (experimental)

コンテナのリソース(`resources`)を指定しなかった場合に、過去の実績に基づき初期のリソースを自動で設定するものです。

- ソース: [plugin/pkg/admission/initialresources](https://github.com/kubernetes/kubernetes/tree/master/plugin/pkg/admission/initialresources)
- 参考: [initial resourcesのproposal](https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/proposals/initial-resources.md)

### `NamespaceLifecycle` :+1:

存在しないnamespaceや削除中のnamespaceへの指定を禁止します。また、デフォルトのnamespaceを削除できないようにします。

- ソース: [plugin/pkg/admission/namespace/lifecycle/admission.go](https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/namespace/lifecycle/admission.go)

## Admissionのプラグインの仕組み

admissionの仕組みは[pkg/admission](https://github.com/kubernetes/kubernetes/tree/master/pkg/admission)で定義されています。Interfaceには`Admit`, `Handles`の2メソッドだけが用意されており、プラグインはこれを実装します。

```go:pkg/admission/interfaces.go
type Interface interface {
    // リクエストの属性(Attributees)を見て、許可の場合nil, 禁止の場合errorを返す
    Admit(a Attributes) (err error)

    // リクエストのオペレーション(CREATE, UPDATE, DELETE, CONNECT)対象とするか
    Handles(operation Operation) bool
}
```

プラグインの登録は各プラグインで`admission.RegisterPlugin`を呼び出すだけです。

```go:plugin/pkg/admission/admit/admission.go
func init() {
    admission.RegisterPlugin("AlwaysAdmit", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
        return NewAlwaysAdmit(), nil
    })
}
```

最終的に各プラグインはチェーン式に呼ばれ、一つでもdenyを返すとそこで終了します。呼び出し元は[pkg/apiserver/resthandler.go](https://github.com/kubernetes/kubernetes/blob/master/pkg/apiserver/resthandler.go)です。

```go:pkg/admission/chain.go
func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
    for _, handler := range admissionHandler {
        if !handler.Handles(a.GetOperation()) {
            continue
        }
        err := handler.Admit(a)
        if err != nil {
            return err
        }
    }
    return nil
}
```


## 参考

- http://kubernetes.io/docs/admin/admission-controllers/
- https://github.com/kubernetes/kubernetes/blob/master/docs/design/admission_control.md