---
title: imgpkg bundle化したHelm ChartをCarvel Package化する
tags: ["Kubernetes", "Carvle", "air-gapped", "kbld", "imgpkg", "Helm", "kapp-controller"]
categories: ["Dev", "Carvel", "kapp-controller"]
date: 2021-12-16T15:00:00Z
updated: 2021-12-16T15:00:00Z
---

> [TUNA-JP Advent Calendar 2021](https://qiita.com/advent-calendar/2021/tuna-jp) その2の17日目のエントリです。

[前の記事](/entries/679)で、imgpkg / kbldを使ったHelm Chartのimgpkg bundle化、及びair-gapped環境へのインストールができることを確認しました。
今回は前回作成したHelm Chartのimgpkg bundleを[Carvel Package](https://carvel.dev/kapp-controller/docs/latest/packaging/) 化し、PackageInstall CRを使ってair-gapped環境にinstallします。

Carvel Packageに関しては [CFCNのWebinar](https://www.youtube.com/watch?v=us3NhfUfIFk) がわかりやすいです。
Webinarで使われたサンプルのレポジトリは https://github.com/k14s/cncf-packaging-webinar です。


**目次**
<!-- toc -->

### imgpkg bundle化したHelm ChartのCarvel Package作成

Webinarの [7-pkg-cr](https://github.com/k14s/cncf-packaging-webinar/tree/develop/7-pkg-cr) と [8-pkg-cr-helm-chart](https://github.com/k14s/cncf-packaging-webinar/tree/develop/8-pkg-cr-helm-chart)
の内容を組み合わせます。

Webinarでは作成されませんでしたが、[PackageMetadata CR](https://carvel.dev/kapp-controller/docs/latest/packaging/#package-metadata) を作っておきます。
`pkg-metadata.yaml`に次の内容を記述します。

```yaml
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: PackageMetadata
metadata:
  name: nginx.chart.pkg.maki.lol
  namespace: default
spec:
  displayName: "Nginx"
  longDescription: "Nginx Bitnami Helm Chart"
  shortDescription: "Nginx Bitnami Helm Chart"
  categories:
  - nginx
  - bitnami
  - helm
```

次に [Package CR](https://carvel.dev/kapp-controller/docs/latest/packaging/#package) を作成します。
`fetch`に`imgpkgBundle`を`template`に`helmTemplate` + `kbld`を指定します。
`pkg.yaml`に次の内容を記述します。

```yaml
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: Package
metadata:
  name: nginx.chart.pkg.maki.lol.9.5.7
  namespace: default
spec:
  refName: nginx.chart.pkg.maki.lol
  version: 9.5.7
  releaseNotes: |
    Supprt nginx bitnami helm chart 9.5.7
  template:
    spec:
      fetch:
      - imgpkgBundle:
          image: harbor-10-213-232-22.sslip.io/library/nginx-chart-bundle:9.5.7
      template:
      - helmTemplate: {}
      - kbld:
          paths:
          - "-"
          - ".imgpkg/images.yml"
      deploy:
      - kapp: {}
```
> ちなみに既存のHelm ChartをCarvel Package化したいだけであれば、imgpkg bundleを作る必要はなく、次のYAMLのように`fetch`に`helmChart`を指定すれば良いです。air-gapped環境にインストールしたい場合は、imgpkg bundle化する
> ```yaml
> apiVersion: data.packaging.carvel.dev/v1alpha1
> kind: Package
> metadata:
>   name: nginx.chart.pkg.maki.lol.9.5.7
>   annotations:
>     kapp.k14s.io/change-group: pkg
> spec:
>   refName: nginx.chart.pkg.maki.lol
>   version: 9.5.7
>   releaseNotes: |
>     Supprt nginx bitnami helm chart 9.5.7
>   template:
>     spec:
>       fetch:
>       - helmChart:
>           name: nginx
>           version: 9.5.7
>           repository:
>             url: https://charts.bitnami.com/bitnami
>       template:
>       - helmTemplate: {}
>       - kbld:
>           paths:
>           - "-"
>           - ".imgpkg/images.yml"
>       deploy:
>       - kapp: {}
> ```

`pkg-metadata.yaml`と`pkg.yaml`をapplyします。

```
kubectl apply -f pkg-metadata.yaml -f pkg.yaml
```

PackageとPackageMetadata一覧に`nginx.chart.pkg.maki.lol`が現れることを確認します。

```
$ kubectl get package        
NAME                                                 PACKAGEMETADATA NAME            VERSION                 AGE
cert-manager.tanzu.vmware.com.1.1.0+vmware.1-tkg.2   cert-manager.tanzu.vmware.com   1.1.0+vmware.1-tkg.2    79h34m56s
contour.tanzu.vmware.com.1.17.1+vmware.1-tkg.1       contour.tanzu.vmware.com        1.17.1+vmware.1-tkg.1   79h34m56s
external-dns.tanzu.vmware.com.0.8.0+vmware.1-tkg.1   external-dns.tanzu.vmware.com   0.8.0+vmware.1-tkg.1    79h34m56s
fluent-bit.tanzu.vmware.com.1.7.5+vmware.1-tkg.1     fluent-bit.tanzu.vmware.com     1.7.5+vmware.1-tkg.1    79h34m56s
grafana.tanzu.vmware.com.7.5.7+vmware.1-tkg.1        grafana.tanzu.vmware.com        7.5.7+vmware.1-tkg.1    79h34m56s
harbor.tanzu.vmware.com.2.2.3+vmware.1-tkg.1         harbor.tanzu.vmware.com         2.2.3+vmware.1-tkg.1    79h34m56s
multus-cni.tanzu.vmware.com.3.7.1+vmware.1-tkg.1     multus-cni.tanzu.vmware.com     3.7.1+vmware.1-tkg.1    79h34m56s
prometheus.tanzu.vmware.com.2.27.0+vmware.1-tkg.1    prometheus.tanzu.vmware.com     2.27.0+vmware.1-tkg.1   79h34m55s
nginx.chart.pkg.maki.lol.9.5.7                       nginx.chart.pkg.maki.lol        9.5.7                   1m6s

$ kubectl get packagemetadata
NAME                            DISPLAY NAME   CATEGORIES                 SHORT DESCRIPTION                                    AGE
cert-manager.tanzu.vmware.com   cert-manager   certificate management     Certificate management                               79h34m58s
contour.tanzu.vmware.com        Contour        ingress                    An ingress controller                                79h34m58s
external-dns.tanzu.vmware.com   external-dns   dns                        This package provides DNS synchronization funct...   79h34m58s
fluent-bit.tanzu.vmware.com     fluent-bit     logging,observability      Fluent Bit is a fast Log Processor and Forwarder     79h34m58s
grafana.tanzu.vmware.com        grafana        monitoring,observability   Visualization and analytics software                 79h34m58s
harbor.tanzu.vmware.com         Harbor         OCI registry               OCI Registry                                         79h34m57s
multus-cni.tanzu.vmware.com     multus-cni     networking                 This package provides the ability for enabling ...   79h34m57s
prometheus.tanzu.vmware.com     prometheus     monitoring,observability   A time series database for your metrics              79h34m58s
nginx.chart.pkg.maki.lol        Nginx          nginx,bitnami,helm         Nginx Bitnami Helm Chart                             1m9s
```

> 【補足情報】<br>
> PackageもPackageMetadataも**Namespaced**なリソースです。上記の例ではnginxは`default` namespaceにインストールされました。
> しかし、一覧表示には`tanzu-package-repo-global`にインストールされたPackageとPackageMetadataも出力されています。<br>
> kapp controllerの`-packaging-global-namespace`オプションに指定されたnamespaceは他のnamespaceからもアクセスできるようになります。<br>
> TKGのkapp-controllerに`-packaging-global-namespace=tanzu-package-repo-global`オプションが指定されているため、このnamespaceにインストールされたPackageやPackageMetadataは他のnamespaceからも見えるようになっています。<br>
> 詳細は [ドキュメント](https://carvel.dev/kapp-controller/docs/latest/package-consumer-concepts/) に記載されています。

### PackageInstall CRの作成

インストールしたPackage CRはPackageInstall CRを作成することで [App CR](https://carvel.dev/kapp-controller/docs/latest/app-overview/) が作成され、Deploymentなどの実際のリソースが作成されます。
PackageInstallの作り方は、YAMLを`kubectl`で作成する方法と`tanzu` CLIを使用する方法があります。

#### kubectlでPackageInstall CRを作成

まずはYAMLでPackageInstall CRを作成します。インストールするPackageの指定と、Packageに渡すパラメータ(ここではhelm templateに渡すvalues.yamlそのもの)をSecret経由で指定します。

`pkg-install.yaml`に次の内容を記述します。

```yaml
apiVersion: packaging.carvel.dev/v1alpha1
kind: PackageInstall
metadata:
  name: nginx
  namespace: default
spec:
  serviceAccountName: default-ns-sa
  packageRef:
    refName: nginx.chart.pkg.maki.lol
    versionSelection:
      constraints: 9.5.7
  values:
  - secretRef:
      name: nginx-values
---
apiVersion: v1
kind: Secret
metadata:
  name: nginx-values
  namespace: default
stringData:
  data.yml: |
    serverBlock: |-
      server {
        listen 0.0.0.0:8080;
        location / {
          return 200 "hello from kapp-controller!";
        }
      }
```

ここで指定した`secretRef`はPackage CRの`template`の一つ目(ここでは`helmTemplate`)に渡されます。


以下のコマンドでPackageInstall CRを作成します。その前にPackageInstall CRが使用するService AccountとそのRBACを作成します。
```
kubectl apply -f https://github.com/k14s/cncf-packaging-webinar/raw/develop/3-app-cr-config-map/rbac/default-ns.yml
kubectl apply -f pkg-install.yaml
```

nginxのPackage Install CRとそれによって作成されるApp CRを確認します。


```
$ kubectl get packageinstall,app                  
NAME                                        PACKAGE NAME               PACKAGE VERSION   DESCRIPTION           AGE
packageinstall.packaging.carvel.dev/nginx   nginx.chart.pkg.maki.lol   9.5.7             Reconcile succeeded   71s

NAME                         DESCRIPTION           SINCE-DEPLOY   AGE
app.kappctrl.k14s.io/nginx   Reconcile succeeded   55s            71s
```

実際にどのようなリソースが作成されたかは次のようにApp CRのstatusで確認できます。

```yaml
$ kubectl get app nginx -oyaml
apiVersion: kappctrl.k14s.io/v1alpha1
kind: App
metadata:
  creationTimestamp: "2021-12-14T11:39:07Z"
  finalizers:
  - finalizers.kapp-ctrl.k14s.io/delete
  generation: 1
  name: nginx
  namespace: default
  ownerReferences:
  - apiVersion: packaging.carvel.dev/v1alpha1
    blockOwnerDeletion: true
    controller: true
    kind: PackageInstall
    name: nginx
    uid: 2aa16ab1-0637-4584-b952-9c2f8d16cf78
  resourceVersion: "970989"
  uid: f35ddafe-858a-415c-bc74-bffa42a5799f
spec:
  deploy:
  - kapp: {}
  fetch:
  - imgpkgBundle:
      image: harbor-10-213-232-22.sslip.io/library/nginx-chart-bundle:9.5.7
  serviceAccountName: default-ns-sa
  template:
  - helmTemplate:
      valuesFrom:
      - secretRef:
          name: nginx-values
  - kbld:
      paths:
      - '-'
      - .imgpkg/images.yml
status:
  conditions:
  - status: "True"
    type: ReconcileSucceeded
  consecutiveReconcileSuccesses: 3
  deploy:
    exitCode: 0
    finished: true
    startedAt: "2021-12-14T11:40:30Z"
    stdout: |-
      Target cluster 'https://100.64.0.1:443'
      11:40:30AM: info: Resources: Ignoring group version: schema.GroupVersionResource{Group:"stats.antrea.tanzu.vmware.com", Version:"v1alpha1", Resource:"networkpolicystats"}
      11:40:30AM: info: Resources: Ignoring group version: schema.GroupVersionResource{Group:"stats.antrea.tanzu.vmware.com", Version:"v1alpha1", Resource:"antreanetworkpolicystats"}
      Changes
      Namespace  Name  Kind  Conds.  Age  Op  Op st.  Wait to  Rs  Ri
      Op:      0 create, 0 delete, 0 update, 0 noop
      Wait to: 0 reconcile, 0 delete, 0 noop
      Succeeded
    updatedAt: "2021-12-14T11:40:31Z"
  fetch:
    exitCode: 0
    startedAt: "2021-12-14T11:40:28Z"
    stdout: |
      apiVersion: vendir.k14s.io/v1alpha1
      directories:
      - contents:
        - imgpkgBundle:
            image: harbor-10-213-232-22.sslip.io/library/nginx-chart-bundle@sha256:d9ef393cf39246e623e833581f7fb9dbf8547b610152da958d80221a23899a84
          path: .
        path: "0"
      kind: LockConfig
    updatedAt: "2021-12-14T11:40:29Z"
  friendlyDescription: Reconcile succeeded
  inspect:
    exitCode: 0
    stdout: |-
      Target cluster 'https://100.64.0.1:443'
      11:40:31AM: info: Resources: Ignoring group version: schema.GroupVersionResource{Group:"stats.antrea.tanzu.vmware.com", Version:"v1alpha1", Resource:"antreanetworkpolicystats"}
      11:40:31AM: info: Resources: Ignoring group version: schema.GroupVersionResource{Group:"stats.antrea.tanzu.vmware.com", Version:"v1alpha1", Resource:"networkpolicystats"}
      Resources in app 'nginx-ctrl'
      Namespace  Name                    Kind           Owner    Conds.  Rs  Ri  Age
      default    nginx                   Deployment     kapp     2/2 t   ok  -   1m
      ^          nginx                   Endpoints      cluster  -       ok  -   1m
      ^          nginx                   Service        kapp     -       ok  -   1m
      ^          nginx-6d58d959c7        ReplicaSet     cluster  -       ok  -   1m
      ^          nginx-6d58d959c7-sbs8f  Pod            cluster  4/4 t   ok  -   1m
      ^          nginx-ltxf4             EndpointSlice  cluster  -       ok  -   1m
      ^          nginx-server-block      ConfigMap      kapp     -       ok  -   1m
      Rs: Reconcile state
      Ri: Reconcile information
      7 resources
      Succeeded
    updatedAt: "2021-12-14T11:40:31Z"
  observedGeneration: 1
  template:
    exitCode: 0
    stderr: |
      resolve | final: docker.io/bitnami/nginx:1.21.3-debian-10-r29 -> harbor-10-213-232-22.sslip.io/library/nginx-chart-bundle@sha256:f21acb4cd83d4b55930d8e290ebd3064fb0e019b0f06cd10ebc297cd67901747
    updatedAt: "2021-12-14T11:40:30Z"
```

`helmTemplate`に`secretRef`が設定されていることも確認できました。

デプロイされたNginxにアクセスし、`data.yaml`で設定した内容が反映されていることを確認します。

```
$ curl $(kubectl get svc nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}')   
hello from kapp-controller!
```

確認できたら、PackageInstallとRBACの設定を削除します。
```
kubectl delete -f pkg-install.yaml
kubectl delete -f https://github.com/k14s/cncf-packaging-webinar/raw/develop/3-app-cr-config-map/rbac/default-ns.yml
```

#### Tanzu CLIでPackageInstall CRを作成

PackageInstall CRは`tanzu` CLIを使うことでも作成できます。この場合PackageInstall CRのYAMLは不要です。
また、RBACの設定も自動で行われます。

次のコマンドで対象のnamespaceにインストール可能なPackage一覧を取得します、
```
$ tanzu package available list
\ Retrieving available packages... 
  NAME                           DISPLAY-NAME  SHORT-DESCRIPTION                                                                                           LATEST-VERSION         
  cert-manager.tanzu.vmware.com  cert-manager  Certificate management                                                                                      1.1.0+vmware.1-tkg.2   
  contour.tanzu.vmware.com       Contour       An ingress controller                                                                                       1.17.1+vmware.1-tkg.1  
  external-dns.tanzu.vmware.com  external-dns  This package provides DNS synchronization functionality.                                                    0.8.0+vmware.1-tkg.1   
  fluent-bit.tanzu.vmware.com    fluent-bit    Fluent Bit is a fast Log Processor and Forwarder                                                            1.7.5+vmware.1-tkg.1   
  grafana.tanzu.vmware.com       grafana       Visualization and analytics software                                                                        7.5.7+vmware.1-tkg.1   
  harbor.tanzu.vmware.com        Harbor        OCI Registry                                                                                                2.2.3+vmware.1-tkg.1   
  multus-cni.tanzu.vmware.com    multus-cni    This package provides the ability for enabling attaching multiple network interfaces to pods in Kubernetes  3.7.1+vmware.1-tkg.1   
  prometheus.tanzu.vmware.com    prometheus    A time series database for your metrics                                                                     2.27.0+vmware.1-tkg.1  
  nginx.chart.pkg.maki.lol       Nginx         Nginx Bitnami Helm Chart                                                                                    9.5.7  
```

Packageに渡すパラメータ(ここではhelm templateに渡すvalues.yamlそのもの)のみYAMLで次のように作成します。



```yaml
cat <<EOF > data.yml 
serverBlock: |-
  server {
    listen 0.0.0.0:8080;
    location / {
      return 200 "hello from kapp-controller!";
    }
  }
EOF  
```

次のコマンドでPackageをインストールします。

```
tanzu package install nginx -p nginx.chart.pkg.maki.lol -v 9.5.7 -f data.yml -n default
```

次のようなログが出力され、RBACの設定やPackageInstall CRが作成されていることがわかります。

```
| Installing package 'nginx.chart.pkg.maki.lol' 
| Getting package metadata for 'nginx.chart.pkg.maki.lol' 
/ Creating service account 'nginx-default-sa' 
/ Creating cluster admin role 'nginx-default-cluster-role' 
/ Creating cluster role binding 'nginx-default-cluster-rolebinding' 
/ Creating secret 'nginx-default-values' 
/ Creating package resource 
\ Waiting for 'PackageInstall' reconciliation for 'nginx' 
| 'PackageInstall' resource install status: Reconciling 


 Added installed package 'nginx'
```

`tanzu` CLIでインストールされたPackage一覧を確認します。

```
$ tanzu package installed list
/ Retrieving installed packages... 
  NAME   PACKAGE-NAME              PACKAGE-VERSION  STATUS               
  nginx  nginx.chart.pkg.maki.lol  9.5.7            Reconcile succeeded  
```

`kubectl`でPackageInstall CRとApp CRを確認します。

```
$ kubectl get packageinstall,app     
NAME                                        PACKAGE NAME               PACKAGE VERSION   DESCRIPTION           AGE
packageinstall.packaging.carvel.dev/nginx   nginx.chart.pkg.maki.lol   9.5.7             Reconcile succeeded   3m53s

NAME                         DESCRIPTION           SINCE-DEPLOY   AGE
app.kappctrl.k14s.io/nginx   Reconcile succeeded   13s            3m53s
```

実際にどのようなリソースが作成されたかは次のようにApp CRのstatusで確認できます。


```yaml
$ kubectl get app nginx -oyaml
apiVersion: kappctrl.k14s.io/v1alpha1
kind: App
metadata:
  creationTimestamp: "2021-12-14T11:47:28Z"
  finalizers:
  - finalizers.kapp-ctrl.k14s.io/delete
  generation: 1
  name: nginx
  namespace: default
  ownerReferences:
  - apiVersion: packaging.carvel.dev/v1alpha1
    blockOwnerDeletion: true
    controller: true
    kind: PackageInstall
    name: nginx
    uid: 0aa10a91-7134-4564-85a3-243d2456f51b
  resourceVersion: "973411"
  uid: d776a09d-0acf-4c0e-a0a0-0dffd49ae719
spec:
  deploy:
  - kapp: {}
  fetch:
  - imgpkgBundle:
      image: harbor-10-213-232-22.sslip.io/library/nginx-chart-bundle:9.5.7
  serviceAccountName: nginx-default-sa
  template:
  - helmTemplate:
      valuesFrom:
      - secretRef:
          name: nginx-default-values
  - kbld:
      paths:
      - '-'
      - .imgpkg/images.yml
status:
  conditions:
  - status: "True"
    type: ReconcileSucceeded
  consecutiveReconcileSuccesses: 8
  deploy:
    exitCode: 0
    finished: true
    startedAt: "2021-12-14T11:51:42Z"
    stdout: |-
      Target cluster 'https://100.64.0.1:443' (nodes: cheetah-control-plane-xtwcj, 1+)
      11:51:43AM: info: Resources: Ignoring group version: schema.GroupVersionResource{Group:"stats.antrea.tanzu.vmware.com", Version:"v1alpha1", Resource:"antreaclusternetworkpolicystats"}
      11:51:43AM: info: Resources: Ignoring group version: schema.GroupVersionResource{Group:"stats.antrea.tanzu.vmware.com", Version:"v1alpha1", Resource:"antreanetworkpolicystats"}
      11:51:43AM: info: Resources: Ignoring group version: schema.GroupVersionResource{Group:"stats.antrea.tanzu.vmware.com", Version:"v1alpha1", Resource:"networkpolicystats"}
      Changes
      Namespace  Name  Kind  Conds.  Age  Op  Op st.  Wait to  Rs  Ri
      Op:      0 create, 0 delete, 0 update, 0 noop
      Wait to: 0 reconcile, 0 delete, 0 noop
      Succeeded
    updatedAt: "2021-12-14T11:51:43Z"
  fetch:
    exitCode: 0
    startedAt: "2021-12-14T11:51:41Z"
    stdout: |
      apiVersion: vendir.k14s.io/v1alpha1
      directories:
      - contents:
        - imgpkgBundle:
            image: harbor-10-213-232-22.sslip.io/library/nginx-chart-bundle@sha256:d9ef393cf39246e623e833581f7fb9dbf8547b610152da958d80221a23899a84
          path: .
        path: "0"
      kind: LockConfig
    updatedAt: "2021-12-14T11:51:42Z"
  friendlyDescription: Reconcile succeeded
  inspect:
    exitCode: 0
    stdout: |-
      Target cluster 'https://100.64.0.1:443' (nodes: cheetah-control-plane-xtwcj, 1+)
      11:51:43AM: info: Resources: Ignoring group version: schema.GroupVersionResource{Group:"stats.antrea.tanzu.vmware.com", Version:"v1alpha1", Resource:"antreaclusternetworkpolicystats"}
      11:51:43AM: info: Resources: Ignoring group version: schema.GroupVersionResource{Group:"stats.antrea.tanzu.vmware.com", Version:"v1alpha1", Resource:"networkpolicystats"}
      11:51:43AM: info: Resources: Ignoring group version: schema.GroupVersionResource{Group:"stats.antrea.tanzu.vmware.com", Version:"v1alpha1", Resource:"antreanetworkpolicystats"}
      Resources in app 'nginx-ctrl'
      Namespace  Name                   Kind           Owner    Conds.  Rs  Ri  Age
      default    nginx                  Deployment     kapp     2/2 t   ok  -   4m
      ^          nginx                  Endpoints      cluster  -       ok  -   4m
      ^          nginx                  Service        kapp     -       ok  -   4m
      ^          nginx-59b74db6b        ReplicaSet     cluster  -       ok  -   4m
      ^          nginx-59b74db6b-wsh68  Pod            cluster  4/4 t   ok  -   4m
      ^          nginx-j9wh8            EndpointSlice  cluster  -       ok  -   4m
      ^          nginx-server-block     ConfigMap      kapp     -       ok  -   4m
      Rs: Reconcile state
      Ri: Reconcile information
      7 resources
      Succeeded
    updatedAt: "2021-12-14T11:51:43Z"
  observedGeneration: 1
  template:
    exitCode: 0
    stderr: |
      resolve | final: docker.io/bitnami/nginx:1.21.3-debian-10-r29 -> harbor-10-213-232-22.sslip.io/library/nginx-chart-bundle@sha256:f21acb4cd83d4b55930d8e290ebd3064fb0e019b0f06cd10ebc297cd67901747
    updatedAt: "2021-12-14T11:51:42Z"
```

デプロイされたNginxにアクセスし、`data.yaml`で設定した内容が反映されていることを確認します。

```
$ curl $(kubectl get svc nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}')   
hello from kapp-controller!
```

確認できたら、PackageInstallとRBACの設定を`tanzu` CLIで削除します。

```
tanzu package installed delete nginx -y
```

---

imgpkg bundle化したHelmChartをCarvel Package化しました。
このPackageはair-gapped環境でもインストール可能です。
