EKSにデプロイしたNexusのblob storeをEBSからS3に移行させる

# はじめに
Sonatype Nexusはmaven, gradle, npm, dockerなどに対応したプライベートリポジトリマネージャーです。
リポジトリ内のファイルの保存方式はFILEとS3のどちらかを選ぶことができ、前者はEBSなどに、後者はその名の通りS3に保存されます。
ディスク容量の監視を不要とし料金も安くしたいときにはS3にしておいた方がいいです。

今回はEBSにFILE方式で保存されている既存データをS3に移行させる方法を説明します。
NexusはEKSにデプロイすることを前提としています。

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

# 前準備
データ移行の前にNexusデプロイ、リポジトリ作成、データ格納を行います。
EKS、S3などのAWSリソースやALB Ingress Controller、kube2iamなどのKubernetesリソースはすでにデプロイ済みであることを前提とします。

## Nexusのデプロイ
Helmを使ってNexusをデプロイします。
templateを少し修正する必要があるので`helm fetch`で公式Helm Chartをローカルに落とします。

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

Nexus Helm Chartのディレクトリ構造は下記となります。
valuesには`my-values.yaml`を自分で作って適用させます。
`*`が付いているファイルを編集します。

```
├── 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 *
```

`ingress.yaml`の編集箇所は`spec.rules[].http.paths[].path`を消去するだけです。
デフォルトでは`path: /`となっていますがこれだとjsファイルを読み込むことができなくなるので除く必要があります。

```yaml:template/ingress.yaml
{{- if .Values.ingress.enabled -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: {{ template "nexus.fullname" . }}
  labels:
{{ include "nexus.labels" . | indent 4 }}
  annotations:
    {{- range $key, $value := .Values.ingress.annotations }}
    {{ $key }}: {{ $value | quote }}
    {{- end }}
spec:
  rules:
{{- if .Values.nexusProxy.env.nexusHttpHost }}
    - host: {{ .Values.nexusProxy.env.nexusHttpHost }}
      http:
        paths:
          - backend:
            {{- if .Values.nexusProxy.svcName }}
              serviceName: {{ .Values.nexusProxy.svcName }}
            {{- else }}
              serviceName: {{ template "nexus.fullname" . }}
            {{- end }}
{{- if .Values.nexusProxy.enabled }}
              servicePort: {{ .Values.nexusProxy.port }}
{{- else }}
              servicePort: {{ .Values.nexus.nexusPort }}
{{- end }}
{{- end }}
{{- if .Values.nexusProxy.enabled -}}
{{- if .Values.nexusProxy.env.nexusDockerHost }}
    - host: {{ .Values.nexusProxy.env.nexusDockerHost }}
      http:
        paths:
          - backend:
            {{- if .Values.nexusProxy.svcName }}
              serviceName: {{ .Values.nexusProxy.svcName }}
            {{- else }}
              serviceName: {{ template "nexus.fullname" . }}
            {{- end }}
              servicePort: {{ .Values.nexusProxy.port }}
{{- end }}
{{- end -}}
{{- with .Values.ingress.rules  }}
{{ toYaml . | indent 2 }}
  {{- end -}}
{{- if .Values.ingress.tls.enabled }}
  tls:
    - hosts:
      {{- if .Values.nexusProxy.env.nexusHttpHost }}
        - {{ .Values.nexusProxy.env.nexusHttpHost }}
      {{- end }}
      {{- if .Values.nexusProxy.env.nexusDockerHost }}
        - {{ .Values.nexusProxy.env.nexusDockerHost }}
      {{- end }}
      {{- if .Values.ingress.tls.secretName }}
      secretName: {{ .Values.ingress.tls.secretName | quote }}
      {{- end }}
{{- end -}}
{{- end }}
```

`my-values.yaml`は下記となります。
すでに述べた通りALB Ingress Controllerとkube2iamがデプロイされていることを前提としていて、PodにはS3FullAccessPolicyが与えられたIAM Roleを付与します。

```yaml:my-values.yaml
statefulset:
  enabled: true
nexus:
  dockerPort: 5003
  nexusPort: 8081
  service:
    type: NodePort
  podAnnotations:
    iam.amazonaws.com/role: s3-full-access-role
nexusProxy:
  enabled: true
  env:
    nexusDockerHost: registry.sample.com
    nexusHttpHost: nexus.sample.com
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
```

次のコマンドでnexusをデプロイします。

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

あとはアクセスするときに名前解決できるようにRoute53等で設定を加えれば良いです。

## リポジトリ作成
Nexusでリポジトリを作成する時にはblob storeを選択する必要があります。
今回はデフォルトで用意されているFILE方式のものを使用します。
このblob storeはEBSにマウントされています。

<img width="1440" alt="スクリーンショット 2020-02-10 18.56.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/44a49703-176e-e4ba-d6a2-3835cdc9a8ec.png">

docker(hosted)とraw(hosted)をリポジトリとして作成していきます。
前者はDockerレジストリ、後者は画像などの格納場所を用途としています。

### ①docker(hosted)
Repositoryからdocker(hosted)を選択します。
<img width="1440" alt="スクリーンショット 2020-02-10 18.56.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/29e6f290-e529-f412-3e8b-428bdc2d4ffc.png">

NameとHTTPだけ埋めて作成します。
HTTP PortはNexusデプロイ時にDockerレジストリのために用意したポートを使用します。
ここでは`5003`です。

<img width="1440" alt="スクリーンショット 2020-02-10 18.58.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/3e95813c-4ac0-db05-fc25-bf6fd303bbaa.png">


問題なく作成されたことを画面で確認します。

<img width="1440" alt="スクリーンショット 2020-02-10 18.59.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/11abd93f-b0fa-7464-bd7b-fce87c021e49.png">


### ②raw(hosted)
Repositoryからraw(hosted)を選択する。
<img width="1440" alt="スクリーンショット 2020-02-10 19.00.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/0033e619-34ca-93ae-0b50-6f877196a004.png">

Nameだけ埋めて作成します。
<img width="1440" alt="スクリーンショット 2020-02-10 19.01.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/74a50d65-62f3-4cd5-0891-5db6f60cde38.png">

問題なく作成されたことを画面で確認します。
<img width="1440" alt="スクリーンショット 2020-02-10 19.02.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/f80928a8-852b-4255-0ef6-280256a7f5d6.png">

## データ格納
Dockerイメージと画像をそれぞれdocker(hosted)、raw(hosted)に格納していきます。

### ①docker(hosted)
dockerコマンドでDockerイメージをアップロードします。
HTTPの場合はクライアント側にinsecure registryの設定を入れないといけないことに注意しましょう。

```console
$ docker login registry.sample.com
$ docker push registry.sample.com/alpine:3.7
```

Nexus画面より、問題なくアップロードされていることを確認します。
<img width="1440" alt="スクリーンショット 2020-02-10 19.07.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/1ce9c795-ccae-d317-305d-7c0f6b20b2ac.png">

### ②raw(hosted)
Nexus画面からraw(hosted)を選択しUpload componentをクリックします。
<img width="1440" alt="スクリーンショット 2020-02-10 19.18.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/4c19fddd-7b30-22ef-be8f-163dbd652cbe.png">

Browseからローカルの画像を選択しUploadします。
<img width="1440" alt="スクリーンショット 2020-02-10 19.20.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/ecdcd73c-4e16-f3d8-6f35-a498459e59a6.png">

こちらも問題なくアップロードされることを確認します。
<img width="1440" alt="スクリーンショット 2020-02-10 19.25.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/5fe55eda-a2b5-a65c-de31-ecf29d4e3ed4.png">


# データ移行
EBSからS3へのデータ移行手順を説明していきます。

## S3バケットの作成
事前にS3バケットを作成しておきます。
Nexusの設定でprefix指定ができるので既存のバケットを使用することも可能です。

```
$ aws s3 mb s3://sonatype-nexus-blobs
```

## S3へのデータ転送
NexusコンテナにAWS CLIが入っていないのでローカルにコピーしてからS3へデータ転送します。

```console
$ mkdir blobs
$ kubectl cp sonatype-nexus-0:/nexus-data/blobs ./blobs
$ aws s3 cp blobs/ s3://sonatype-nexus-blobs --recursive
```

## NexusのDBの編集
NexusのDBを手動で変更しblobsの保存方式をFILEからS3にしていきます。
Nexusにアクセスできないよう事前にServiceの削除等をしておくことをおすすめします。

```console
## Nexusコンテナ内にプロセスを起動させる
$ kubectl exec sonatype-nexus-0 -it /bin/bash

## NexusのDBに接続する
$ cd /opt/sonatype/nexus
$ java -jar lib/support/nexus-orient-console.jar
> connect plocal:/nexus-data/db/config admin admin

## 保存方式をS3に変更する
> update repository_blobstore set type="S3" where name="default"

## リージョンとS3バケット情報を入れる
> update repository_blobstore set attributes.s3={region: 'ap-northeast-1', bucket: 'sonatype-nexus-blobs', prefix: 'default', expiration: -1}  where name="default"
```

上記手順を終えたらPodの再起動をします。

```
$ kubectl delete pod sonatype-nexus-0
```

# 確認
Nexus画面でblob storeを確認します。
TypeがS3になっていることが分かります。
<img width="1440" alt="スクリーンショット 2020-02-10 19.57.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/6334e6b8-8fd2-50cd-17f3-40aad8ae18d1.png">

Dockerレジストリを確認します。
docker pullできることが分かります。

```
$  docker pull registry.sample.com/alpine:3.7
3.7: Pulling from alpine
Digest: sha256:ab3fe83c0696e3f565c9b4a734ec309ae9bd0d74c192de4590fd6dc2ef717815
Status: Downloaded newer image for registry.moguta.ml/alpine:3.7
```

rawリポジトリは画面で確認します。
Pathから画像ファイルを選択します。
<img width="1440" alt="スクリーンショット 2020-02-15 17.27.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/b82c1e67-aa98-c56d-8219-2aad0c6bafef.png">


別タブで画像が表示されます。
<img width="1440" alt="スクリーンショット 2020-02-15 17.28.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/1cc52f60-bf74-7b94-35bb-07250f009990.png">


# おわりに
EKSにHelmでデプロイしたNexusのblob storeをEBSからS3へ移行させる手順を説明しました。
blob storeの中身が大きいとその分ローカルストレージの容量を確保しないといけないことに注意です。



# 参考
[Nexus Repository Manager 3 Storage Guide](https://help.sonatype.com/repomanager3/configuration/storage-guide)
[sonatype Nexusのblob store を FileからS3に変更する](https://paihu.hatenablog.com/entry/2019/08/16/144037)