LoginSignup
0

More than 1 year has passed since last update.

AKSをTerraformでセットアップしてプライベートレジストリからコンテナをPullするまで

Last updated at Posted at 2020-12-05

AzureのAKSにコンテナをDocker HubなどからPullするのは、特に設定などしなくてもできますが、もし社内のプライベートレジストリなどにVPN経由で行うと、おそらく初回は失敗する思います。ごく簡単な回避方法をメモしてみました。できるだけ、手作業を省いて、再構築の可能性なども考慮してCLIだけですべてを完成できるよう行ってみました。AzureにはAzure Container Registryのサービスが提供されていますが、今回はすでに会社内でプライベートレジストリがある場合を想定しました。

TerraformでAKSにクラスターを設定

AKSのクラスターはTerraformを使って設定します。詳しくは、以下をご参考にしてください。

aks.tf
resource "azurerm_kubernetes_cluster" "app_aks" {
    name                = "${var.prefix}-${local.cluster_name}"
    resource_group_name = data.azurerm_resource_group.aps_cluster.name
    location            = data.azurerm_resource_group.aps_cluster.location
    dns_prefix          = local.cluster_name
    kubernetes_version  = local.k8s_version
    api_server_authorized_ip_ranges = ["101.xxx.xxx.0/22","124.xx.xxx.xxx/28"]

    node_resource_group = "${var.prefix}-${var.environment}-nodes"

    network_profile {
        network_plugin      = "kubenet"
        pod_cidr            = "171.30.0.0/16"
        service_cidr        = "171.31.0.0/16"
        dns_service_ip      = "171.31.0.10"
        docker_bridge_cidr  = "171.31.0.1/16"
        load_balancer_sku   = "Standard"
        outbound_type       = "userDefinedRouting"
    }

    default_node_pool {
        name                    = local.cluster_name
        vm_size                 = local.vm_size
        type                    = "VirtualMachineScaleSets"
        availability_zones      = [1,2,3]
        node_count              = local.node_count
        os_disk_size_gb         = 50
        vnet_subnet_id          = data.azurerm_subnet.aps_cluster.id
        max_pods                = local.max_pods
        enable_auto_scaling     = false
        enable_node_public_ip   = false
    }

    service_principal {
        client_id     = var.client_id
        client_secret = var.client_secret
    }
    addon_profile {
        oms_agent {
            enabled = false
        }

        kube_dashboard {
            enabled = false 
        }
    }
    role_based_access_control {
        enabled = true
        azure_active_directory {
            managed = true
            admin_group_object_ids = [var.mycompany_ad_group]
        }
    }
    tags = local.tags
    depends_on = [
        data.azurerm_route_table.aps_cluster
    ]
}

これを設定することによって、ルーティングを設定して自社のVPNとかにつなげたりします。

outbound_type       = "userDefinedRouting"

この辺りは今回の本題ではないので、詳しいところは省きます。

コンテナPullのテスト

テスト用のYamlファイルを作成。
Namespaceを作ってそこにbusyboxをデプロイ。これは特に問題がなく立ち上がると思います。

aks-test.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: myspace

---
kind: Pod
apiVersion: v1
metadata:
  name: app-test
  namespace: myspace
spec:
  containers:
   - name: app-test-pod
     image: busybox
     command: ["/bin/sh", "-c"]
     args:
      - tail -f /dev/null
---
kind: Pod
apiVersion: v1
metadata:
  name: app-test1
  namespace: myspace
spec:
  containers:
   - name: app-test-pod
     image: containers.name.company/alpine:latest
     command: ["/bin/sh", "-c"]
     args:
      - tail -f /dev/null

問題は二つ目のPodです。これは会社のプライベートレジストリで、当然アドレスも会社内で使っているものです。

image: containers.name.company/alpine:latest

ネットワークなどの設定はすべて正常に行われると仮定しても、AKSでクラスターを作っていきなりこのコンテナをPullしようとしても、おそらくImagePullBackOffのようなエラーが出ると思います。
エラーメッセージとかもあまり親切ではなく、何が問題なのか気付くまでちょっと時間がかかってしまいそうな感じです。

トラブルシューティング

まず最初にしたのは通信が正常に行えているか。先ほどbusyboxをデプロイしているのでそれを使って

telnet containers.name.company 443

とかすると、レジストリ向けに443のポートがつながっているか確認できます。

次にcurlをつかって実際レジストリにアクセスしてみます。busyboxwgetではTLS認証がテストできないので、新たにテスト用のPodをひとつ作ります。

curl.yaml
kind: Pod
apiVersion: v1
metadata:
  name: app-test
  namespace: myspace
spec:
  containers:
   - name: app-test-pod
     image: alpine
     command: ["/bin/sh", "-c"]
     args:
      - tail -f /dev/null

先ずcurlをインストール

apk update
apk add curl

次にcurlを使っていTLS認証のテストをします。

curl https://containers.name.company

curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

こんな感じのエラーがきっと出ると思います。書いていることは公開鍵を発行しているlocalの発行元を確認できませんってことです。当然会社のプライベートのアドレスなので、デフォルトでalpineに会社のルート証明書があるはずはありません。

自分だったら、おそらくこのような手順で何が問題か探っていくと思われます。ただ、お気づきの方もいると思いますが、最後のcurlを使ったテストはあまり問題解決とは直接関係ないことが分かります。コンテナ上からcurlでアクセスできた場合はポートが開いていて通信ができるということと、レジストリが反応しているということが分かりますが、なぜコンテナをPullできないかまではたどり着けません。

ただ、このテストで出たTLSの認証の失敗が、コンテナをPullしているときにも起こっているのではと言うヒントにはなります。コンテナをPullするのはK8Sのノードであって、その上にあるコンテナではありません。このことからK8Sのノード上に、プライベートレジストリの公開鍵を発行した会社のルート証明書がないのが原因ではと想像できます。

kubectlを使ってルート証明書をK8Sのシークレットにデプロイ

次にAKSのクラスターにいくつかリソースをデプロイします。本当はcloud-initをつかって以下のことができればいいのですが、やり方が見つかりませんでした。ご存知の方がいたら、コメント欄で教えていただければありがたいです。

kubectl -n myspace create secret generic company-ca --from-file=CompanyCA.crt
kubectl -n myspace create secret generic company-global-ca --from-file=CompanyGlobalRootCA.crt

2つのルート証明書をK8Sのシークレットにデプロイします。これはcontainers.name.companyの公開鍵がCompanyCAで発行され、CompanyCAのルート証明書がCompanyGlobalRootCAであるためです。
このルート証明書がK8SのノードにないためにPullをする時に、SSL certificate problemのようなエラーがノード側で発生して正常に動作していないからと考えられるからです。レジストリ側で中間CACompanyCAを正しく設定されていれば、本来ならCompanyCAをデプロイする必要はありませんが、これがあると他のシステムとかで中間CAの設定がされていないような場合でも対処できたりします。

ルート証明書をK8Sのノードにインストール

最後に、K8Sのシークレットにデプロイしたルート証明書をK8Sのノードにインストールします。最初のTerraformでlinux_profileを使ってユーザを設定していませんので、基本的にノードに直接sshなどでアクセスすることはないというのが前提です。これはセキュリティ対策のポリシー次第ですが、必要のないアクセスは設定しない方がセキュリティ的には良いかと思います。

root-ca.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: root-ca
  namespace: myspace 
  labels:
    k8s-app: root-ca
spec:
  selector:
    matchLabels:
      name: root-ca
  template:
    metadata:
      labels:
        name: root-ca
    spec:
      containers:
      - name: root-ca
        image: busybox
        command: ["/bin/sh", "-c"]
        args:
         - /bin/cp /home/ca1/companyCA_G2.crt /etc/docker/certs.d/containers.name.company/company-ca.crt;
           /bin/cp /home/ca2/CompanyGlobalRootCA.crt /etc/docker/certs.d/containers.name.company/company-global-ca.crt;
           find /etc/docker;
           tail -f /dev/null
        volumeMounts:
        - name: etc-docker
          mountPath: /etc/docker/certs.d/containers.name.company
        - name: company-ca
          mountPath: /home/ca1
        - name: company-global-ca
          mountPath: /home/ca2
      volumes:
      - name: etc-docker
        hostPath:
          path: /etc/docker/certs.d/containers.name.company
      - name: company-ca
        secret:
          secretName: company-ca
      - name: company-global-ca
        secret:
          secretName: company-global-ca   

Daemonsetを使うことによってすべてのノードにPodが配置され、コマンドで設定されている/etc/docker/certs.d/containers.name.companyにルート証明書がコピーされます。

Podが正常に起動したら、ログにコピーされたルート証明書のリストが表示されるので、それですべてのノードにインストールされたかどうか確認できます。

ここまで終えると、先ほどPullに失敗していたコンテナも正常に起動すはずです。

image: containers.name.company/alpine:latest

最後に

このDaemonsetは、この後は必要はないので消去します。

kubectl -n myspace delete ds root-ca

AKSで使われるノードにプライベートイメージを使うことができれば、プライベートイメージに最初からルート証明書をインストールしておけば手間は省けるのですが、みたところプライベートイメージをAKSで使うことはサポートされていないようです。

参考文献

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0