Nexus Helm Chartを複数のDockerレジストリに対応できるようにする

## はじめに
Sonatype Nexusはmaven, gradle, npm, dockerなどに対応したプライベートリポジトリマネージャーです。
権限制御も充実しているのでこれを採用している企業も多いようです。

[公式Helm Chart](https://github.com/helm/charts/tree/master/stable/sonatype-nexus)もあるのでKubernetes上に簡単にデプロイすることができます。
しかしながらデフォルトのChartではDockerレジストリを1つしか作成することができません。
そこで今回はNexus Helm Chartを改変して複数のDockerレジストリに対応できるようにします。

使用したコードは[GitHub](https://github.com/samskeyti88/helm-nexus)にあげてあります。

## 環境情報
macOS Mojave 10.14.1
Helm: 3.0.1
EKS: 1.14
Nexus Helm Chart: 1.22.0

## アーキテクチャ構成
公式のNexus Helm Chartをそのままデプロイすると下図の構成になります。
`nexus.sample.com`はHTTPでNexusにアクセスする際のドメインで`docker.sample.com`はDockerレジストリにアクセスする際のドメインです。
また、ALB Ingress Controllerは独自にデプロイしたものでありNexus Helm Chartには含まれません。

<img width="689" alt="スクリーンショット 2020-02-23 16.37.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/f0dd014c-f05b-8bfd-245e-6a5fb2afa9cf.png">


Podの中にはNexusのコンテナに加えてNexus Proxyというコンテナが含まれます。
Nexus Proxyはその名の通りプロキシとして機能し、下記の環境変数を設定することでHTTPホストとDockerホストのルーティングを行います。

* `NEXUS_HTTP_HOST`
* `UPSTREAM_HTTP_PORT`
* `NEXUS_DOCKER_HOST`
* `UPSTREAM_DOCKER_PORT`

使用する環境変数が定められているのでDockerレジストリは1つに限られてしまうのが問題となります。
実際の業務においてはリリース用や開発用のレジストリで分けて誤リリースを避けたいというケースもあるかと思います。


今回は公式のHelm Chartを改修して下図のような構成にします。
至って普通の構成です。(そもそも何故公式ではプロキシコンテナ使ってるのか謎)

<img width="682" alt="スクリーンショット 2020-02-23 16.37.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/ea3c4b50-fa5d-c933-1167-3bfaf1924de1.png">


## Helm Chart改修
まずはNexus Helm Chartを取得します。templateを編集するためローカルに持って来る必要があります。

```console
$ helm fetch stable/sonatype-nexus --version 1.22.0
$ tar -xvzf sonatype-nexus-1.22.0.tgz
```

ディレクトリ構成は以下になります。デプロイ時は`values.yaml`を使わず自分で作成した`my-values.yaml`を使います。
`*`が付いているファイルを今回編集していきます。

```console
.
├── Chart.yaml
├── README.md
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── backup-pv.yaml
│   ├── backup-pvc.yaml
│   ├── backup-secret.yaml
│   ├── configmap.yaml
│   ├── deployment-statefulset.yaml *
│   ├── ingress.yaml *
│   ├── proxy-ks-secret.yaml
│   ├── proxy-route.yaml
│   ├── proxy-svc.yaml *
│   ├── pv.yaml
│   ├── pvc.yaml
│   ├── route.yaml
│   ├── secret.yaml
│   ├── service.yaml *
│   └── serviceaccount.yaml
├── values.yaml
└── my-values.yaml *
```

### `values`

まずは`my-values.yaml`を作成します。

```yaml:my-values.yaml
statefulset:
  enabled: true
nexus:
  livenessProbe:
    port: 8081
  readinessProbe:
    port: 8081
  hosts:
  - host: nexus.sample.com
    port: 8081
  - host: docker-dev.sample.com
    port: 5003
  - host: docker-release.sample.com
    port: 15003
nexusProxy:
  enabled: false
ingress:
  enabled: true
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
  tls:
    enabled: false
service:
  enabled: true
  serviceType: NodePort
```

`nexus.livenessProbe.port`、`nexus.readinessProbe.port`、`nexus.hosts`は自分で定義したkeyとなります。Ingress、Service、StatefulSetに使われます。
Ingress ControllerにはALB Ingress Controllerを使用します。これはデプロイされている前提として進めます。


### `template`

続いてtemplateを編集していきます。

`proxy-svc.yaml`は`.Values.nexusProxy.enabled=false`にするだけでプロキシ用のServiceが作成されないようにします。

```diff:proxy-svc.yaml
- {{- if or .Values.nexusProxy.enabled .Values.ingress.enabled }}
+ {{- if .Values.nexusProxy.enabled }}
```

`ingress.yaml`は自分で定義した`.Values.nexus.hosts`が同一のServiceにルーティングされるように下記内容を追記します。

```diff:ingress.yaml
spec:
  rules:
+     {{- range .Values.nexus.hosts }}
+     - host: {{ .host }}
+       http:
+         paths:
+           - backend:
+               serviceName: {{ template "nexus.name" $ }}-service
+               servicePort: {{ .port}}
+     {{- end }}
```

`service.yaml`も同様に自分で定義した`.Values.nexus.hosts`の値が反映されるように編集します。

```diff:service.yaml
spec:
  ports:
-   {{- with .Values.service.ports  }}
- {{ toYaml . | indent 2 }}
-   {{- end }}
+   {{- range .Values.nexus.hosts }}
+   - name: port-{{ .port }}
+     port: {{ .port }}
+     targetPort: {{ .port }}
+   {{- end }}
```

`deployment-statefulset.yaml`でも同様です。既存の`containerPort`は不要なので取り除きます。

```diff:deployment-statefulset.yaml
spec:
  template:
    spec:
      containers:
        - name: nexus
          ports:
-             - containerPort: {{ .Values.nexus.dockerPort }}
-               name: nexus-docker-g
-             - containerPort: {{ .Values.nexus.nexusPort }}
-               name: nexus-http
+             {{- range .Values.nexus.hosts }}
+             - name: port-{{ .port }}
+               containerPort: {{ .port }}
+             {{- end }}
          livenessProbe:
            httpGet:
-               port: {{ .Values.nexus.nexusPort }}
+               port: {{ .Values.nexus.livenessProbe.port }}
          readinessProbe:
            httpGet:
-               port: {{ .Values.nexus.nexusPort }}
+               port: {{ .Values.nexus.readinessProbe.port }}
```

### デプロイ
あとはhelmコマンドでデプロイするだけです。

```console
$ helm install --values ./my-values.yaml sonatype-nexus ./
```

Ingressで定義したドメインについて名前解決できるようRoute53等で設定すればNexusにアクセスできるようになります。

<img width="1440" alt="スクリーンショット 2020-02-09 19.33.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/440aa3c2-8e4b-15a3-5f90-b2ac68a5fcbc.png">


## Dockerレジストリ作成
Dockerレジストリ作成はNexusの画面から行う必要があります。

adminユーザでログインし、Repositories設定画面からリポジトリ作成をします。
<img width="1440" alt="スクリーンショット 2020-02-09 19.14.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/e79a2c1a-c684-b588-e9ad-c768fa275d8a.png">


Recipeにはdocker(hosted)を選択します。
<img width="1440" alt="スクリーンショット 2020-02-09 19.17.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/a4d86604-2dd9-c04c-9d58-23d90b6e7196.png">


詳細設定はNameとHTTP Portだけ入力して他はデフォルトで問題ありません。
<img width="1440" alt="スクリーンショット 2020-02-09 19.20.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/4b1acc45-3bcb-8087-eaa2-3f7cd2a8e81a.png">

「Create repository」を押せば完了です。Dockerレジストリが作成されていることが確認できます。
<img width="1440" alt="スクリーンショット 2020-02-09 19.23.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/7b58dac8-c037-3026-3362-cac499fec21c.png">

開発時用のDockerレジストリを作成したので同様の手順でリリース用のものも作成します。

## 検証
実際に2つのDockerレジストリを検証します。
まずはローカルにDockerイメージを落とします。

```console
$ docker pull alpine:3.9

$ docker images
REPOSITORY  TAG  IMAGE ID      CREATED      SIZE
alpine      3.9  82f67be598eb  2 weeks ago  5.53MB
```

タグ付けしてdocker pushします。
HTTPの場合はinsecure registryに登録することに注意しましょう。

```console
$ docker tag 82f67be598eb docker-dev.sample.com/alpine:3.7
$ docker push docker-dev.sample.com/alpine:3.7

$ docker tag 82f67be598eb docker-release.sample.com/alpine:3.7
$ docker push docker-release.sample.com/alpine:3.7
```

画面から2種類のDockerレジストリにイメージが格納されていることが確認できます。
<img width="1440" alt="スクリーンショット 2020-02-09 20.31.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/6db2cc3e-ba86-e2fc-e53b-2bc2745506e3.png">
<img width="1440" alt="スクリーンショット 2020-02-09 20.32.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/56bd7710-a856-1e64-85f4-27d729280a8a.png">


## おわりに
公式のNexus Helm Chartを改修して複数Dockerレジストリに対応できることを確認しました。
Helm Chartのtemplateの編集箇所が多いとバージョンが上がったときに都度対応しなくてはならないのが面倒ですが、今回についてはしょうがないかなと思います。