在 K8s 中部署 Nexus 私服仓库

介绍

Nexus 是用于管理和托管软件包的私有仓库管理系统。它提供了一个集中化的存储位置,用于保存软件组件、库文件、依赖项和其他二进制文件。

仓库类型

Nexus 仓库按用途分为四种类型:

  • Blob Stores:用于存储实体数据的仓库。
  • Hosted:本地仓库,可用于上传和下载本地构件。
  • Proxy:代理仓库,可通过代理仓库下载外部仓库的构件。
  • Group:仓库组,可包含本地和代理仓库的集合。

其他参考

Maven 相关配置参考:简书

Nexus 开启 https 参考:cnblogs

部署

Nexus 3.x 版本支持 Docker 镜像仓库,并且使用 OrientDB 数据库来存储仓库数据。部署选择最新版 3.38.1。

准备工作

声明应用名和环境,建立发布文件存放目录:

[root@k8s-101 ~]# export APPNAME=nexus
[root@k8s-101 ~]# export APPENV=local
[root@k8s-101 ~]# mkdir -p /hxz393/${APPENV}/${APPNAME}/apply

创建 SVC

Web 端口和 Docker 上传下载端口都可用 Nginx 做反向代理,直接通过域名操作,但对网络速度要求很高。如果网络不稳,会出现大容量镜像推送时中断,陷入反复重试循环。因此还是优先通过内网端口推送,将端口暴露出来:

[root@k8s-101 ~]# tee /hxz393/${APPENV}/${APPNAME}/apply/${APPNAME}-svc.yaml<<EOF
apiVersion: v1
kind: Service
metadata:
  name: ${APPNAME}-svc
  namespace: ${APPENV}
spec:
  type: NodePort
  ports:
  - name: web
    port: 80
    targetPort: web
    nodePort: 8081
  selector:
    app: ${APPNAME}
---
apiVersion: v1
kind: Service
metadata:
  name: ${APPNAME}-svc-docker-pull
  namespace: ${APPENV}
spec:
  type: NodePort
  ports:
  - name: docker-pull
    port: 80
    targetPort: docker-pull
    nodePort: 10007
  selector:
    app: ${APPNAME}
---
apiVersion: v1
kind: Service
metadata:
  name: ${APPNAME}-svc-docker-push
  namespace: ${APPENV}
spec:
  type: NodePort
  ports:
  - name: docker-push
    port: 80
    targetPort: docker-push
    nodePort: 10008
  selector:
    app: ${APPNAME}
EOF
[root@k8s-101 ~]# kubectl apply -f /hxz393/${APPENV}/${APPNAME}/apply/${APPNAME}-svc.yaml
[root@k8s-101 ~]# kubectl -n ${APPENV} get svc -owide | grep ${APPNAME}

创建 STS

Nexus 对内存和储存消耗很大,在生产环境需要配置大一点:

[root@k8s-101 ~]# tee /hxz393/${APPENV}/${APPNAME}/apply/${APPNAME}-sts.yaml<<EOF
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: ${APPNAME}-sts
  namespace: ${APPENV}
spec:
  serviceName: ${APPNAME}-svc
  replicas: 1
  selector:
    matchLabels:
      app: ${APPNAME}
  template:
    metadata:
      labels:
        app: ${APPNAME}
    spec:
      terminationGracePeriodSeconds: 60
      nodeSelector:
        NodeEnv: ${APPENV}
      containers:
      - name: ${APPNAME}
        image: sonatype/nexus3:3.38.1
        imagePullPolicy: IfNotPresent
        resources:
          requests:
            memory: 500Mi
          limits:
            memory: 4200Mi
        ports:
          - name: web
            containerPort: 8081
          - name: docker-pull
            containerPort: 10007
          - name: docker-push
            containerPort: 10008
        env:
          - name: TZ
            value: Asia/Shanghai
        securityContext:
          runAsUser: 0
          runAsGroup: 0
        startupProbe:
          tcpSocket:
            port: web
          timeoutSeconds: 6
          failureThreshold: 60
          periodSeconds: 5
        readinessProbe:
          tcpSocket:
            port: web
          timeoutSeconds: 10
          periodSeconds: 35
        livenessProbe:
          tcpSocket:
            port: web
          timeoutSeconds: 10
          periodSeconds: 60
        volumeMounts:
        - name: data
          mountPath: /nexus-data/
          subPath: data/
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      storageClassName: kube-sc
      accessModes: [ "ReadWriteMany" ]
      resources:
        requests:
          storage: 50Gi
EOF
[root@k8s-101 ~]# kubectl apply -f /hxz393/${APPENV}/${APPNAME}/apply/${APPNAME}-sts.yaml
[root@k8s-101 ~]# kubectl -n ${APPENV} get sts,po -owide | grep ${APPNAME}
[root@k8s-101 ~]# kubectl -n ${APPENV} logs -f ${APPNAME}-sts-0

第一次部署时间比较长。

配置域名

配置前端访问域名 nexus.x2b.net

[root@k8s-101 ~]# vi /hxz393/local/nginx/config/conf.d/nexus.x2b.net.conf
server {
    listen 80;
    listen 443 ssl;
    server_name nexus.x2b.net;

    ssl_certificate   /cert/x2b.net.pem;
    ssl_certificate_key  /cert/x2b.net.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1.2 TLSv1.3 SSLv3;
    ssl_prefer_server_ciphers on;
    location / {
        proxy_pass http://nexus-svc.local/;
        proxy_set_header Host   $host;
        proxy_set_header X-Real-IP      $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto "https";
    }
}

配置专用域名 docker.x2b.net 用于 Docker 镜像拉取推送:

[root@k8s-101 ~]# vi /hxz393/local/nginx/config/conf.d/docker.x2b.net.conf
upstream docker_get {
    server nexus-svc-docker-pull.local;
}
 
upstream docker_put {
    server nexus-svc-docker-push.local;
}

server {
    listen 80;
    listen 443 ssl;
    server_name docker.x2b.net;

    ssl_certificate   /cert/x2b.net.pem;
    ssl_certificate_key  /cert/x2b.net.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1.2 TLSv1.3 SSLv3;
    ssl_prefer_server_ciphers on;

    chunked_transfer_encoding on;

    # 设置默认使用推送代理
    set $upstream "docker_put";
    # 当请求是GET也就是拉取镜像的时候,这里改为拉取代理,如此便解决了拉取和推送的端口统一
    if ( $request_method ~* 'GET') {
        set $upstream "docker_get";
    }
    location / {
        proxy_pass http://$upstream;
        proxy_set_header Host $host;
        proxy_connect_timeout 3600;
        proxy_send_timeout 3600;
        proxy_read_timeout 3600;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_buffering off;
        proxy_request_buffering off;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto http;
    }
}
[root@k8s-101 ~]# for i in $(kubectl get pod -n local|grep nginx|awk '{print $1}');do kubectl exec -it -n local $i -- nginx -s reload; done

配置

配置仓库和策略等。

修改密码

第一次部署后,获取管理员 admin 默认登录密码:

[root@k8s-101 ~]# kubectl -n local exec -it nexus-sts-0 -- cat /nexus-data/admin.password
b0736b15-cde5-4ab0-b880-b21767b99366

登陆后点击用户管理重设密码。

禁止匿名访问

默认情况下匿名用户可以看到仓库内容。点击设置中的 Security > Anonymous Access,去除勾选允许匿名访问(Allow anonymous users to access the server)再保存。

添加权限

点击设置中的 Security > Realms,将左侧的认证方式全部加入到右边,点击保存。

配置 Docker 仓库

为了不被混淆,先把默认库都删除。

建立 Blob 库

在 Blob Stores 页面点击新建:

  • Type:选择 File 表示本地文件储存。
  • Name:输入仓库名 docker-blob

点击保存完成。

建立 Hosted 库

在 Repositories 页面点击新建,类型选择 docker(hosted)

  • Name:输入仓库名 docker-hosted
  • HTTP:设置端口为 10008
  • Blob store:选择刚才新建的仓库 docker-blob

默认策略为 Allow redeploy 允许覆盖上传,点击创建完成。

建立 Proxy 库

在 Repositories 页面点击新建,类型选择 docker(proxy)

  • Name:输入仓库名 docker-proxy-dockerio
  • Remote storage:输入官方 docker 仓库地址 https://registry-1.docker.io
  • Docker Index:选择使用 Docker Hub 索引。
  • Blob store:选择仓库 docker-blob

点击创建来完成。同理可以创建阿里云镜像仓库代理,地址为用阿里云账号申请的私有地址。

建立 Group 库

在 Repositories 页面点击新建,类型选择 docker(group)

  • Name:输入仓库名 docker-group
  • HTTP:设置端口为 10007
  • Blob store:选择仓库 docker-blob
  • Member repositories:逐个添加代理和本地仓库到右边成员列表。hosted 库放最上面。

点击创建完成。

创建 Secret

K8s 中使用私有仓库需要指定仓库用户名和密码。先创建 Secret:

[root@k8s-101 ~]# kubectl create secret docker-registry nexussecret --docker-server=192.168.1.253:10007 --docker-username='admin' --docker-password='9pFmd,C@DP2hZJ*O' --namespace=local
[root@k8s-101 ~]# kubectl get secret -n local

用打补丁的方式,把 Secret 分配给不同命名空间默认 SA,这样在发布文件中不需要指定 imagePullSecrets 参数了:

[root@k8s-101 ~]# kubectl patch sa default --namespace=test -p '{"imagePullSecrets": [{"name": "nexussecret"}]}'

创建 Secret(另类)

另外一种更规范创建 Secret 的方式。先登录仓库:

[root@k8s-101 ~]# docker login 192.168.1.253:10007

将用户密码记录文件转为 base64 编码:

[root@k8s-101 ~]# cat /root/.docker/config.json|base64 -w 0
ewoJImF1dGhzIjogewoJCSIxOTIuMTY4LjEuMjUzOjEwMDA3IjogewoJCQkiYXV0aCI6ICJZV1J0YVc0Nk9YQkdiV1FzUTBCRVVESm9Xa29xVHc9PSIKCQl9LAoJCSIxOTIuMTY4LjEuMjUzOjEwMDA4IjogewoJCQkiYXV0aCI6ICJZV1J0YVc0Nk9YQkdiV1FzUTBCRVVESm9Xa29xVHc9PSIKCQl9LAoJCSJkb2NrZXIuemhvbmdtZWl6aHV6YW8uY29tIjogewoJCQkiYXV0aCI6ICJZV1J0YVc0Nk9YQkdiV1Fz

再建立 Secret 将登录信息储存:

[root@k8s-101 ~]# vi secret-login.yaml
apiVersion: v1
kind: Secret
metadata:
  name: dockerlogin
  namespace: dev
data:
  .dockerconfigjson: ewoJImF1dGhzIjogewoJCSIxOTIuMTY4LjEuMjUzOjEwMDA3IjogewoJCQkiYXV0aCI6ICJZV1J0YVc0Nk9YQkdiV1FzUTBCRVVESm9Xa29xVHc9PSIKCQl9LAoJCSIxOTIuMTY4LjEuMjUzOjEwMDA4IjogewoJCQkiYXV0aCI6ICJZV1J0YVc0Nk9YQkdiV1FzUTBCRVVESm9Xa29xVHc9PSIKCQl9LAoJCSJkb2NrZXIuemhvbmdtZWl6aHV6YW8uY29tIjogewoJCQkiYXV0aCI6ICJZV1J0YVc0Nk9YQkdiV1Fz
type: kubernetes.io/dockerconfigjson

之后通过 imagePullSecrets 参数来指定使用。

配置 Maven 仓库

Maven 仓库可以按照实际情况,建立多个 hosted 和 group 库,来区分不同构建环境。

建立 Blob 库

创建一个 Maven 专用 Blob 库:

  • Type:选择 file
  • Name:输入 maven-blob

点击保存完成。

建立 Hosted 库

在 Repositories 页面点击新建,类型选择 maven2(hosted)

  • Name:输入 maven2-hosted-local
  • Version policy:选择 Mixed
  • Blob store:选择 maven-blob
  • Deployment policy:选择 Allow redeploy 来允许重复发布。

点击创建完成。

建立 Proxy 库

在 Repositories 页面点击新建,类型选择 maven2(proxy)

  • Name:输入 maven2-proxy-aliyun
  • Remote storage:填阿里云镜像仓库地址 https://maven.aliyun.com/repository/public
  • Blob store:选择 maven-blob

点击创建完成。同理创建一个 maven 官方库代理 maven2-proxy-maven,地址为 https://repo1.maven.org/maven2/

建立 Group 库

在 Repositories 页面点击新建,类型选择 maven2(group)

  • name:输入 maven2-group-local
  • Version Policy:选择 Mixed 混合。
  • Blob store:选择 maven-blob
  • Member repositories:将对应仓库以 hosted 库在前,proxy 库在后的方式加入到右边列表。

点击创建完成。

配置 npm 仓库

当构建前端项目的时候,常常会指定镜像地址:--registry=https://registry.npm.taobao.org ,使用私服做代理可以节约流量和时间。

建立 Blob 库

创建一个 npm 专用 Blob 库:

  • Type:选择 file
  • Name:输入 npm-blob

点击保存完成。

建立 Hosted 库

在 Repositories 页面点击新建,类型选择 npm(hosted)

  • Name:输入仓库名 npm-hosted-local
  • Blob store:选择仓库 npm-blob
  • Deployment policy:选择 Allow redeploy 允许重复发布。

点击创建完成。

建立 Proxy 库

在 Repositories 页面点击新建,类型选择 npm(proxy)

  • Name:输入 npm-proxy-npm
  • Remote storage:填官方仓库地址 https://registry.npmjs.org
  • Blob store:选择 npm-blob

点击创建完成。另外建立一个指向淘宝镜像站 https://registry.npm.taobao.org 的代理仓库 npm-proxy-taobao

建立 Group 库

在 Repositories 页面点击新建,类型选择 npm(group)

  • name:输入 npm-group-local
  • Blob store:选择 npm-blob
  • Member repositories:将左侧仓库以 hosted 库在前的方式加入到右边列表。

点击创建完成。

清理策略

私有仓库如果不及时清理,空间会越来越大。在 Cleanup Policies 中,可以建立针对仓库的清理策略。这里只清理 Jar 包:

  • 名称:输入名称为 maven2-clean
  • 格式:选择格式为 maven2
  • 清理标准:设置清理策略 Component Usage 为 7 天内未被下载。

点击保存完成。返回仓库列表,根据需要对 Hosted 库进行策略配置。

清理任务

清理任务同样可以进行清理操作。在 System > Tasks 中建立清理 maven2 的任务:

  • 类型选择(Select a Type):选择 Maven - Delete SNAPSHOT
  • 任务名称(Task name):输入 clean-maven2-hosted-local
  • 仓库(Repository):选择 maven2-hosted-local
  • 最小快照数(Minimum snapshot count):设置为保留 2 个副本。
  • 快照保留期限(Snapshot retention days):保留多少天内的包,设置为 0。
  • 任务频率(Task frequency):任务运行频率选择每天 01:45 运行。

创建一个清理 Blob 仓库的策略,该策略将清理标记为删除的原始存储数据:

  • 类型选择(Select a Type):选择 Admin - Compact blob store,删除未被使用的数据。
  • 任务名称(Task name):任务名称设为 clean-maven-blob
  • Blob 仓库(Repository):选择仓库 maven-blob
  • 任务频率 Task frequency):任务运行频率选择每天 02:45 运行。

类似地,可以创建 Docker Blob 仓库清理的任务。Docker 镜像不需要设置清除策略。

清理工具

使用第三方开源工具 nexus-cli 对私服中的镜像进行清理。项目地址:Github

下载

在 release 页面下载适用系统版本的可执行程序。添加权限后测试运行:

[root@k8s-101 ~]# mv nexus-cli-v1.1.0-linux-amd64 nexus-cli && chmod 744 nexus-cli
[root@k8s-101 ~]# ./nexus-cli -h
NAME:
   Nexus CLI - Manage Docker Private Registry on Nexus

USAGE:
   nexus-cli [global options] command [command options] [arguments...]

AUTHORS:
   Mohamed Labouardy <mohamed@labouardy.com>
   Alexandr Zaytsev <13rentgen@gmail.com>

COMMANDS:
   configure  Configure Nexus Credentials
   image      Manage Docker Images
   help, h    Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h  show help

登录

使用 nexus-cli 尝试登录私服:

[root@k8s-101 ~]# ./nexus-cli configure
Enter Nexus Host: http://192.168.1.253:8081
Enter Nexus Repository Name: docker-hosted
Enter Nexus Username: admin
Enter Nexus Password: 9pFmd,C@DP2hZJ*O

工具会在当前目录下生成 .credentials 文件:

[root@k8s-101 ~]# cat .credentials
# Nexus Credentials
nexus_host = "http://192.168.1.253:8081"
nexus_username = "admin"
nexus_password = "9pFmd,C@DP2hZJ*O"
nexus_repository = "docker-hosted"

查询

尝试列出所有镜像:

[root@k8s-101 ~]# ./nexus-cli image ls
base/nginx
base/openjdk
base/python
dev/config-admin-web
test/devops-health-check

查询指定镜像的所有标签:

[root@k8s-101 ~]# ./nexus-cli image tags -name test/devops-health-check
e6befa3 created at 2022-08-16 13:55:39 +0000 GMT
def95a6 created at 2022-08-16 15:44:10 +0000 GMT
...
There are 28 images for test/devops-health-check

删除

可以通过 -keep 参数来设置需要保留的版本个数。例如只保留 test/devops-health-check 镜像 5 个最新镜像:

[root@k8s-101 ~]# ./nexus-cli image delete -name test/devops-health-check -keep 5
test/devops-health-check:e6befa3 image will be deleted ...
test/devops-health-check:e6befa3 has been successful deleted
test/devops-health-check:def95a6 image will be deleted ...

脚本

用脚本对所有镜像进行清理,只保留 5 个版本:

[root@k8s-101 ~]# vi clean.sh
for image in `/nexus-cli image ls|grep -vE "Total images"`
do
    /nexus-cli image delete -n $image -k 5
done
[root@k8s-101 ~]# chmod 744 clean.sh

发布

在 Gitlab 上新建 nexus-clean 项目,并将所需文件提交上去:

[root@k8s-101 ~]# mkdir nexus-clean
[root@k8s-101 ~]# mv nexus-cli .credentials clean.sh nexus-clean/

在 Jenkins 流水线目录下新建 Dockerfile

[root@k8s-101 ~]# mkdir -p /hxz393/local/jenkins/pipeline/devops-nexus-clean
[root@k8s-101 ~]# vi /hxz393/local/jenkins/pipeline/devops-nexus-clean/Dockerfile
FROM 192.168.1.253:10007/alpine:3.14
COPY . /
CMD [ "sh", "/clean.sh" ]

新建 K8s 发布文件,定义为定时任务类型,每小时运行一次:

[root@k8s-101 ~]# vi /hxz393/local/jenkins/pipeline/devops-nexus-clean/Kubefile.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: <APP_NAME>
  namespace: <NAME_SPACE>
  labels:
    app: <APP_NAME>
  annotations:
    kubernetes.io/change-cause: "<GIT_TAG>"
spec:
  schedule: "* */1 * * *"
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            app: <APP_NAME>
        spec:
          restartPolicy: OnFailure
          containers:
          - name: <APP_NAME>
            image: <NEXUS_ADDRESS>/hxz393-<NAME_SPACE>/<APP_NAME>:<GIT_TAG>

新建 Jenkins 发布文件:

[root@k8s-101 ~]# vi /hxz393/local/jenkins/pipeline/devops-nexus-clean/Jenkinsfile
pipeline {
  agent {
    kubernetes {
      yamlFile 'local/jenkins/config/docker-pod.yaml'
    }
  }
  environment{
      // ###############################手动填写部分###############################
      GITLAB_URL='git@gitlab-svc.test:devops/nexus-clean'                   // Git项目地址
      NAME_SPACE="local"                                     // 命名空间
      // ###############################自动生成部分###############################
      APP_NAME="${JOB_BASE_NAME}"                                           // 项目名
  }
  stages {
    stage('准备工作') {
      steps {
        script {
          if (env.NAME_SPACE == "local") {
            echo "将发布到本地环境"
            env.GIT_BRANCH_NAME="master"                                           // 分支名
            env.NEXUS_ADDRESS='192.168.1.253:10007'                              // 私服地址
            env.NEXUS_ADDRESS_PUSH='192.168.1.253:10008'                         // 私服上传地址
            env.CREDENTIALS_ID='Nexus'                                           // 私服账号
          }
          else {                                                          
            echo "不支持的环境"                                         
          }
        }
        sh "cp -r test/jenkins/pipeline/${APP_NAME}/ ../pipeline"
        git branch: "${GIT_BRANCH_NAME}", credentialsId: 'Gitlab', url: "${GITLAB_URL}"
        sh 'cp -r ../pipeline/* .'
        script {
          env.GIT_TAG = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()                     // 提交哈希
          env.GIT_COMMIT_MSG = sh (returnStdout: true, script: 'git log -1 --pretty=%B ${GIT_TAG}').trim()      // 提交日志
        }
        sh '''
        sed -i "s/<NEXUS_ADDRESS>/${NEXUS_ADDRESS}/g" Kubefile.yaml
        sed -i "s/<APP_NAME>/${APP_NAME}/g" Kubefile.yaml
        sed -i "s/<NAME_SPACE>/${NAME_SPACE}/g" Kubefile.yaml
        sed -i "s/<GIT_TAG>/${GIT_TAG}/g" Kubefile.yaml
        '''
        echo "代码拉取完成!"
      }
    }
    stage('构建镜像') {
      steps {
        container('docker') {
          withCredentials([usernamePassword(credentialsId: "${CREDENTIALS_ID}", passwordVariable: 'Password', usernameVariable: 'Username')]) {
            sh "docker login ${NEXUS_ADDRESS_PUSH} -u ${Username} -p ${Password}"
            sh "docker build --progress=plain --no-cache -t ${NEXUS_ADDRESS_PUSH}/nanruan-${NAME_SPACE}/${APP_NAME}:${GIT_TAG} ."
            sh "docker push ${NEXUS_ADDRESS_PUSH}/nanruan-${NAME_SPACE}/${APP_NAME}:${GIT_TAG}"
          }
        }
        echo "镜像构建完成!"
      }
    }
    stage('发布应用') {
      steps {
        container('kubectl') {
          sh "kubectl --kubeconfig /hxz393/${NAME_SPACE}/k8s/config/admin.conf apply -f Kubefile.yaml"
        }
        sh "mkdir -p /hxz393/${NAME_SPACE}/${APP_NAME}/"
        sh "cp Dockerfile Kubefile.yaml Jenkinsfile /hxz393/${NAME_SPACE}/${APP_NAME}/"
        echo "应用发布完成!"
      }
    }
  }
}

最后是在 Jenkins 中新建同名任务 devops-nexus-clean ,发布后清理任务会保持运行。

测试

测试私服仓库上传下载功能。在 CI/CD 中应用私服请查看 Jenkins 相关主题。

测试 Docker 仓库

测试登录、拉取和上传。

测试登录

先使用 docker login 来测试是否可以登录。使用 IP 登录时候注意:10007 是 docker pull 端口,10008 是 docker push 端口,登陆时候需要分别认证:

[root@k8s-103 ~]# docker login -u 'admin' -p '9pFmd,C@DP2hZJ*O' docker.x2b.net
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
[root@k8s-103 ~]# docker login -u 'admin' -p '9pFmd,C@DP2hZJ*O' 192.168.1.253:10007
[root@k8s-103 ~]# docker login -u 'admin' -p '9pFmd,C@DP2hZJ*O' 192.168.1.253:10008

登录成。测试完记得删除储存的 json 文件 /root/.docker/config.json

测试上传

使用 docker tag 修改镜像标签并上传:

[root@k8s-103 ~]# docker tag mongo:5.0.8 docker.x2b.net/mongo:5.0.8
[root@k8s-103 ~]# docker push docker.x2b.net/mongo:5.0.8
[root@k8s-103 ~]# docker tag redis:5.0.14-alpine 192.168.1.253:10008/redis:5.0.14-alpine
[root@k8s-103 ~]# docker push 192.168.1.253:10008/redis:5.0.14-alpine

测试下载

先在另外一台主机尝试拉取刚刚上传的镜像:

[root@k8s-248 ~]# docker pull docker.x2b.net/mongo:5.0.8
[root@k8s-248 ~]# docker pull 192.168.1.253:10007/redis:5.0.14-alpine

再测试拉取本地和仓库中都没有的镜像:

[root@k8s-248 ~]# docker pull docker.x2b.net/rabbitmq
[root@k8s-248 ~]# docker pull 192.168.1.253:10007/httpd

拉取速度甚至比本地直接拉取还要快。从外部仓库下载的镜像储存在私服 docker-proxy 库的 library 目录中。

测试认证

要测试在 K8s 集群中自动拉取镜像是否工作,先删除所有服务器上的 /root/.docker/config.json 文件:

[root@k8s-248 ~]# rm -rf /root/.docker/config.json

kubectl run 命令建立一个临时 Pod,测试从私服拉取本地没有的镜像:

[root@k8s-101 ~]# kubectl -n local run testnexus --image=192.168.1.253:10007/ibmcom/busybox:1.30.1 --command -- ls -la
pod/testnexus created
[root@k8s-101 ~]# kubectl -n test describe po testnexus
Events:
  Type     Reason     Age              From               Message
  ----     ------     ----             ----               -------
  Normal   Scheduled  34s              default-scheduler  Successfully assigned local/testnexus to k8s-103
  Normal   Pulling    34s              kubelet            Pulling image "192.168.1.253:10007/ibmcom/busybox:1.30.1"
  Normal   Pulled     8s               kubelet            Successfully pulled image "192.168.1.253:10007/ibmcom/busybox:1.30.1" in 25.424388112s
[root@k8s-101 ~]# kubectl -n local logs testnexus
total 16
drwxr-xr-x    1 root     root            17 May  5 12:55 .
drwxr-xr-x    1 root     root            17 May  5 12:55 ..
-rwxr-xr-x    1 root     root             0 May  5 12:55 .dockerenv
drwxr-xr-x    2 root     root         12288 Feb 14  2019 bin
[root@k8s-101 ~]# kubectl -n local delete po testnexus 
pod "testnexus" deleted

镜像已经成功拉取并运行。

测试 npm 仓库

可以运行一个 npm 容器来测试 npm 仓库功能:

[root@k8s-101 ~]# docker run --rm -it 192.168.1.253:10007/node:14.19.3 bash

npm 命令参考:CSDN 博客

查看状态

查看全局安装模块的目录:

root@4f5ec7bfe603:/# npm root -g
/usr/local/lib/node_modules

查看当前登录 npm 的账户,用来 publish 发布包时使用:

root@4f5ec7bfe603:/# npm whoami
admin

测试登录

测试登录私服:

root@4f5ec7bfe603:/# npm login --registry=http://192.168.1.253:8081/repository/npm-group-local/
Username: admin
Password: 9pFmd,C@DP2hZJ*O
Email: (this IS public) hxz393@x2b.net
Logged in as admin on http://192.168.1.253:8081/repository/npm-group-local/.
root@4f5ec7bfe603:/# npm login --registry=http://192.168.1.253:8081/repository/npm-hosted-local/

添加用户。在发布前需要添加用户到 hosted 仓库:

root@4f5ec7bfe603:/# npm adduser --registry=http://192.168.1.253:8081/repository/npm-hosted-local/

配置文件

获取全局配置内容:

root@4f5ec7bfe603:/# npm config list -l
; cli configs
long = true
metrics-registry = "https://registry.npmjs.org/"
scope = ""
user-agent = "npm/6.14.17 node/v14.19.3 linux x64"

获取 npm 缓存目录和仓库地址。全局配置中的其他选项都可以通过 npm config get 来获取:

root@4f5ec7bfe603:/# npm config get cache
/root/.npm
root@4f5ec7bfe603:/# npm config get registry
https://registry.npmjs.org/

npm 获取配置的优先级高低顺序:

  • 命令行参数,如 --registry=

  • 环境变量。

  • 用户配置文件。在使用命令进行属性配置时被写入的配置文件。路径如下:

    root@4f5ec7bfe603:/# npm config get userconfig
    /root/.npmrc
    root@4f5ec7bfe603:/# cd && cat .npmrc 
    //192.168.1.253:8081/repository/npm-group-local/:_authToken=NpmToken.7285beb7-2f92-3295-8ccf-8020132d6232
    //192.168.1.253:8081/repository/npm-hosted-local/:_authToken=NpmToken.7285beb7-2f92-3295-8ccf-8020132d6232
    registry=http://192.168.1.253:8081/repository/npm-group-local/
  • 全局配置文件。需要手动建立,或在使用命令配置时加入 --global 参数来写入。路径如下:

    root@4f5ec7bfe603:~# npm config get globalconfig
    /usr/local/etc/npmrc
  • 自带配置文件。一般是 /usr/local/lib/node_modules/npm/node_modules 下面的 npmrc 文件。

  • 默认配置参数。

配置仓库

配置镜像仓库地址为私服 group 库的地址:

root@4f5ec7bfe603:~# npm config set registry http://192.168.1.253:8081/repository/npm-group-local/

发布时指定地址为 hosted 库的地址。只在本条命令有效:

root@4f5ec7bfe603:~# npm publish --registry=http://192.168.1.253:8081/repository/npm-hosted-local/

测试打包

建立项目目录后,先生成 package.json 文件:

root@4f5ec7bfe603:~# mkdir xyz-hxz393 && cd xyz-hxz393
root@4f5ec7bfe603:~/xyz-hxz393# npm init -y
Wrote to /root/xyz-hxz393/package.json:

{
  "name": "xyz-hxz393",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

生成 index.js 文件:

root@4f5ec7bfe603:~/xyz-hxz393# echo "module.exports = 907;">index.js

试着发布包:

root@4f5ec7bfe603:~/xyz-hxz393# npm publish --registry=http://192.168.1.253:8081/repository/npm-hosted-local/
npm notice 
npm notice package: xyz-hxz393@1.0.0
npm notice === Tarball Contents === 
npm notice 22B  index.js    
npm notice 224B package.json
npm notice === Tarball Details === 
npm notice name:          xyz-hxz393                              
npm notice version:       1.0.0                                   
npm notice package size:  302 B                                   
npm notice unpacked size: 246 B                                   
npm notice shasum:        609171152a95a18623d918869049f5bbcb57aad4
npm notice integrity:     sha512-x1KcX1CvXs9cn[...]fmr7Kx1Lr2d9Q==
npm notice total files:   2                                       
npm notice 
+ xyz-hxz393@1.0.0

登录 Nexus 仓库网页端,浏览 npm-hosted-local 仓库,此处已经有了上传的包 xyz-hxz393/-/xyz-hxz393-1.0.0.tgz

测试下载

测试从私服拉取上传的包,需要重启新的容器:

root@4f5ec7bfe603:~/xyz-hxz393# exit
[root@k8s-101 ~]# docker run --rm -it 192.168.1.253:10007/node:14.19.3 bash
root@26a681c3ecc3:/# npm config get registry
https://registry.npmjs.org/
root@26a681c3ecc3:/# npm config set registry http://192.168.1.253:8081/repository/npm-group-local/
root@26a681c3ecc3:/# cd && npm adduser --registry=http://192.168.1.253:8081/repository/npm-group-local/
Username: admin
Password: 
Email: (this IS public) hxz393@x2b.net
Logged in as admin on http://192.168.1.253:8081/repository/npm-group-local/.

下载私有包:

root@26a681c3ecc3:~# npm -loglevel info install xyz-hxz393
+ xyz-hxz393@1.0.0
added 1 package in 1.83s
npm timing npm Completed in 2017ms
npm info ok 

下载公共包:

root@26a681c3ecc3:~# npm -d install bootstrap
+ bootstrap@5.2.3
added 1 package from 2 contributors in 3.444s

1 package is looking for funding
  run `npm fund` for details

npm timing npm Completed in 3650ms
npm info ok 

查询包下载目录:

root@26a681c3ecc3:~# find / -name bootstrap
/root/node_modules/bootstrap

至此功能测试完毕。

管理

Nexus 在 K8s 集群中的管理。

小版本升级

如果没有大版本跳跃,可以直接升级。例如,从版本 3.38.1 升级到最新的 3.54.1 版本,先删除旧版本,修改镜像版本再重新部署:

[root@k8s-101 ~]# kubectl delete -f /hxz393/${APPENV}/${APPNAME}/apply/
statefulset.apps "nexus-sts" deleted
service "nexus-svc" deleted
service "nexus-svc-docker-pull" deleted
service "nexus-svc-docker-push" deleted
[root@k8s-101 ~]# vi /hxz393/${APPENV}/${APPNAME}/apply/nexus-sts.yaml 
...
      containers:
      - name: nexus
        image: sonatype/nexus3:3.54.1
        imagePullPolicy: IfNotPresent
...
[root@k8s-101 ~]# kubectl apply -f /hxz393/${APPENV}/${APPNAME}/apply/

大版本升级

按照官方指南,2.x 版本不能直接升级到 3.x 版本,需要先升级到 2.x 的最后一个版本(下载地址),然后再升级到 3.x 的最新版本:官网教程

数据迁移

升级迁移可能需要很长时间,可以先运行一个 3.x 版本的 Nexus 副本,新建 proxy 库指向老仓库地址。下面是一个示例:

  • 新建 maven2 proxy 仓库。
  • Name:输入 maven2-proxy-old-maven-public
  • Remote storage:填旧仓库组地址 http://192.168.1.55:8081/repository/maven-public/
  • Blob store:选择 maven-blob
  • HTTP Authentication:输入旧仓库用户名和密码。
  • 创建完毕后,修改现在的 group 库 maven2-group-local 配置,将库 maven2-proxy-old-maven-public 加入到激活列表。

这样配置后,通过新库 maven2-group-local 拉取到旧库的组件,会存入到新库 maven-blob 中。

升级步骤

升级流程简单描述如下:

  • 先部署一个全新的 3.x 版本 Nexus 容器。
  • 在 2.x 版本 Nexus 主页,点击 System > Capabilities > Create capability 来新建 Upgrade:Agent。配置 Token 为 123456
  • 在 3.x 版本 Nexus 主页,点击 System > Capabilities > Create capability 来新建 Upgrade。
  • 在 3.x 版本 Nexus 主页 System 下面多出一个 Upgrade 菜单,点击运行升级向导。在 URL 中输入旧仓库地址,例如:http://192.168.2.110:8081/nexus。在 Access Token 中输入 123456。继续点击下一步,选择空白的目标仓库。再下一步,选择旧 Nexus 的源仓库。最后点击开始等待同步完成。