环境说明

  • helm version: v3.3.1
  • kubernetes: v1.17.9
  • nexus: 3.29.0

部署

准备 storageClass

必要 操作,可以选择手动创建 pvc & pv,如果想部署 nfsStorageClass 的话,请参考早期整理的 文档。此篇文档实战部署步骤中基于 nfsStorageClass 进行实现。如果是用于 生产环境 的话,还是建议使用 ssd 或 iops 较高的 磁盘 作为数据盘使用,数据盘管理方式可以使用 localPv or hostPath 绑定到对应节点进行运行。

使用 helm 进行安装

添加 helm 仓库

1
2
3
helm repo add stable https //charts.helm.sh/stable

helm repo update

设置部署 values.yaml 配置文件

可以执行下面命令进行查看 默认 模板部署文件,或点击此 链接 查看。

1
helm show values stable/sonatype-nexus

修改后,完整 values.yaml 文件展示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
cat > prod-values.yaml << EOF
statefulset:  # 使用 statefulset 进行部署
  enabled: true

nexus:
  imageName: sonatype/nexus3
  imageTag: 3.30.1 
  imagePullPolicy: IfNotPresent
  env:
    - name: install4jAddVmParams
      value: "-Xms1200M -Xmx1200M -XX:MaxDirectMemorySize=2G -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
    - name: NEXUS_SECURITY_RANDOMPASSWORD
      value: "false"
    - name: TZ
      value: "Asia/Shanghai"
  resources: 
    limits:
     cpu: 4
     memory: 8192Mi
    requests:
     cpu: 250m
     memory: 4800Mi

persistence:
  enabled: true
  storageClass: "nfs-retain"
  storageSize: "100Gi"

service:
  name: nexus-service
  enabled: true
  serviceType: ClusterIP
  ports:
  - name: manage   # 此处为 service 对应暴露的端口, 如需暴露其他端口时,向下进行添加即可。
    targetPort: 8081
    port: 8081
  - name: docker-proxy
    targetPort: 8082
    port: 8082
  - name: docker-dev
    targetPort: 8083
    port: 8083
  - name: docker-qa  
    targetPort: 8084
    port: 8084
  - name: docker-prod
    targetPort: 8085
    port: 8085
  - name: docker-custom
    targetPort: 8086
    port: 8086
nexusProxy:
  enabled: false
EOF

创建部署应用

1
2
3
kubectl create ns nexus

helm upgrade --install nexus  -f ./prod-values.yaml  -n nexus stable/sonatype-nexus # 部署安装

image-20210523162416847

使用 traefik 将其暴露

traefik 部署安装使用,请参考 此篇文档

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
cat nexus-ingressroute.yaml 
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: nexus-mirror
  namespace: nexus
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`mirror.treesir.pub`) && PathPrefix(`/`)
    kind: Rule
    services:
    - name: nexus-service
      port: 8081

kubectl apply -f ./nexus-ingressroute.yaml 

image-20210523162902742

初始化U/P: admin: admin123

Docker 私服的配置与优化

此步骤文档,记录创建 customdevqaprod 环境的 docker 私服,并配置常见 docker proxy 类型私服,进行中间镜像的缓存进行使用。

仓库规划

docker 私服端口 使用规划

私服名称 私服作用 私服类型 私服端口
docker-custom 存放,自定义 push的 镜像,与项目环境无关 hostd 8086
idocker.io 代理仓库 & custom 仓库的集合 group 8082
docker-dev 存放与项目dev环境镜像 hostd 8083
docker-qa 存放与项目qa环境镜像 hostd 8084
docker-prod 存放与项目prod环境镜像 hostd 8085

docker 代理仓库列表

名称 私服类型 说明 地址
docker-google proxy google 公开镜像 (需扶墙) https://gcr.io
docker-k8s proxy kubernetes 的官方 google 镜像源(需扶墙) https://k8s.gcr.io
docker-aliyun proxy aliyun 同步 docker 官方源 (存在部分镜像未同步问题) https://7bezldxe.mirror.aliyuncs.com
docker-official proxy dockerhub 官方镜像地址(限制带宽,触发条件:匿名用户100次,认证用户200次) https://registry-1.docker.io

私服创建

创建 hostd 类型私服

且示例创建 docker-custom , 其他仓库创建步骤是一样的,且需更具实际使用情况 更改 Deployment policy 策略 & 是否开启 匿名拉取镜像 即可,如在 prod 环境下,设置策略为 Disable redeploy ,即不允许对已存在的镜像做覆盖操作,push 时将提示错误。

  • 创建 docker 使用的 blob 存储

    建议对不同仓库类型的 私服做一下,blob存储的隔离 (可选)

    image-20210608120921089

  • 选择创建 hostd 类型 docker 私服

    image-20210608120857805

    image-20210608121148991

    image-20210608121249427

创建 proxy 类型代理私服

且示例创建 docker-official 私服,因官方对匿名用户做了拉取到100次后,将降低带宽的使用,我们可以在配置的过程中,为其添加用户认证来增加拉取限制。

  • 选择创建 proxy 类型 docker 私服

    image-20210608121818153

    image-20210608122129261

    image-20210608122321922

    image-20210608122431537

创建 group 类型私服

创建完成 对应 的 proxy 类型的私服后,就可以进行 group 类型私服, 一组仓库的集合的创建了。

  • 组集合列表,如下所示

    image-20210608122810520

  • 创建 group 组 idocker.io

    image-20210608122923180

    image-20210608123030930

    image-20210608123121924

配置 neuxs3 开启 使用 docker的认证管理

默认情况下,docker 认证在 neuxs3 中是没有被打开的,这里需要配置开启一下才行,不然会导致 执行 docker login 不成功。

image-20210610112948251

image-20210610113029463

image-20210610113048664

使用 ingress 对端口进行暴露

在使用 traefik 暴露的docker私服中,因默认开启了 443 端口的监听,此时 docker 私服将默认使用 websecure 入口上的 ingressroute 路由规则,如不添加对应证书的话,将导致 match 上的规则不被匹配

创建 tls 证书生成脚本

gen-cert.sh 证书签发脚本,如下所示:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#!/bin/bash
# scripts from https://www.jianshu.com/p/52fedb82ef53

usage()  
{  
    echo "Usage: $0 [-a [rsa|ecc]] [-d <domain>] [-n <name>] [-h]"  
    echo "  Options:"
    echo "    -a  algorithm.[rsa|ecc]"
    echo "    -d  domain.example: xxx.com,abc.org,*.abc.org"
    echo "    -n  server key name"   
    echo "    -h  help"  
    exit 1  
} 

srv_key_name="server"

while getopts "a:d:n:h" arg #选项后面的冒号表示该选项需要参数
do
    case $arg in
        a)
            alg=$OPTARG #算法
            ;;
        d)
            all_domain=$OPTARG #域名,逗号分隔
            ;;
        n)
            srv_key_name=$OPTARG #服务器证书名称
            ;;
        h)
            usage
            exit 0
            ;;
        ?)  #当有不认识的选项的时候arg为?
            usage
            exit 1
            ;;
    esac
done

domain="domain.com"
san="DNS:*.${domain},DNS:${domain}"
if [ -n "${all_domain}" ]; then
    #分割域名
    OLD_IFS="$IFS"  
    IFS="," 
    domain_array=($all_domain)
    IFS="$OLD_IFS"  

    domain_len=${#domain_array[@]} 
      
    domain=${domain_array[0]}
    san=""
    for ((i=0;i<domain_len;i++))
   {
    if [ $i = 0 ];then
        san="DNS:${domain_array[i]}"
    else
        san="${san},DNS:${domain_array[i]}"
    fi
   }
fi

ca_subj="/C=CN/ST=Hubei/L=Wuhan/O=MY/CN=MY CA"
server_subj="/C=CN/ST=Hubei/L=Wuhan/O=MY/CN=${domain}"
#其中C是Country,ST是state,L是local,O是Organization,OU是Organization Unit,CN是common name
days=14610 # 有效期40年
echo "san:${san}"

sdir="certs"
ca_key_file="${sdir}/ca.key"
ca_crt_file="${sdir}/ca.crt"
srv_key_file="${sdir}/${srv_key_name}.key"
srv_csr_file="${sdir}/${srv_key_name}.csr"
srv_crt_file="${sdir}/${srv_key_name}.crt"
srv_p12_file="${sdir}/${srv_key_name}.p12"
srv_fullchain_file="${sdir}/${srv_key_name}-fullchain.crt"
cfg_san_file="${sdir}/san.cnf"


#algorithm config
if [[ ${alg} = "rsa" ]] ; then
    rsa_len=2048
elif [[ ${alg} = "ecc" ]] ; then
    ecc_name=prime256v1
else 
    usage 
    exit 1
fi     #ifend

echo "algorithm:${alg}"

mkdir -p ${sdir}

if [ ! -f "${ca_key_file}" ]; then
    echo  "------------- gen ca key-----------------------"
    if [[ ${alg} = "rsa" ]] ; then
        openssl genrsa -out ${ca_key_file} ${rsa_len}
    elif [[ ${alg} = "ecc" ]] ; then
        openssl ecparam -out ${ca_key_file} -name ${ecc_name} -genkey
    fi     #ifend

    openssl req -new -x509 -days ${days} -key ${ca_key_file} -out ${ca_crt_file} -subj "${ca_subj}"
fi


if [ ! -f "${srv_key_file}" ]; then
    echo  "------------- gen server key-----------------------"
    if [[ ${alg} = "rsa" ]] ; then
        openssl genrsa -out ${srv_key_file} ${rsa_len}
    elif [[ ${alg} = "ecc" ]] ; then
        openssl ecparam -genkey -name ${ecc_name} -out ${srv_key_file}
    fi     #ifend

    openssl req -new  -sha256 -key ${srv_key_file} -out ${srv_csr_file} -subj "${server_subj}"

    printf "[ SAN ]\nauthorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nkeyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment\nsubjectAltName=${san}" > ${cfg_san_file}
    openssl x509 -req  -days ${days} -sha256 -CA ${ca_crt_file} -CAkey ${ca_key_file} -CAcreateserial -in ${srv_csr_file}  -out ${srv_crt_file} -extfile ${cfg_san_file} -extensions SAN
    cat ${srv_crt_file} ${ca_crt_file} > ${srv_fullchain_file}

    openssl pkcs12 -export -inkey ${srv_key_file} -in ${srv_crt_file} -CAfile ${ca_crt_file} -chain -out ${srv_p12_file}
fi

脚本使用用法

1
./gen-cert.sh -a 算法 -d 域名 -n 证书文件名

执行脚本生成 tls 证书文件

由于 traefik 在使用 secret 资源对象引用证书文件时,名称必须是 tls.crttls.key,这里我们在创建的时候就将名称设置为 tls

1
2
3
4
5
6
chmod a+x gen-cert.sh

./gen-cert.sh -a ecc -d idocker.io,*.idocker.io -n tls

ls certs/  # 在对应目录下,有如下文件即可
ca.crt  ca.key  ca.srl  san.cnf  tls.crt  tls.csr  tls-fullchain.crt  tls.key  tls.p12

将生成的证书,使用 secret 资源对象进行存储

1
2
3
kubectl create secret tls idocker-tls --cert=certs/tls.crt --key=certs/tls.key -n nexus

kubectl get secret idocker-tls -n nexus -o yaml 

image-20210608145341615

更新 traefik ingress 资源对象

最终,完整 nexus-ingressroute.yaml 配置文件如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: nexus-mirror
  namespace: nexus
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`mirror.treesir.pub`) && PathPrefix(`/`)
    kind: Rule
    services:
    - name: nexus-service
      port: 8081

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: nexus-idocker-http
  namespace: nexus
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`idocker.io`) || (Host(`idocker.io`) && Method(`GET`)) 
    kind: Rule
    services:
    - name: nexus-service
      port: 8082
  - match: (Host(`idocker.io`) && Path(`/v1/search`)) || (Host(`idocker.io`) && Method(`PUT`,`HEAD`,`POST`,`PATCH`))
    kind: Rule
    priority: 100  # 权重值提高,在执行 curl https://idocker.io/v1/search?q=nginx 时,确认路由至 dokcer-custom 私服中
    services:
    - name: nexus-service
      port: 8086
  - match: Host(`dev.idocker.io`) && PathPrefix(`/`)
    kind: Rule
    services:
    - name: nexus-service
      port: 8083
  - match: Host(`qa.idocker.io`) && PathPrefix(`/`)
    kind: Rule
    services:
    - name: nexus-service
      port: 8084
  - match: Host(`prod.idocker.io`) && PathPrefix(`/`)
    kind: Rule
    services:
    - name: nexus-service
      port: 8085
  tls:
    secretName: idocker-tls

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: redirect-https
  namespace: nexus
spec:
  redirectScheme:
    scheme: https

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name:  nexus-idocker-https
  namespace: nexus
spec:
  entryPoints:
    - web
  routes:
  - match: HostRegexp(`idocker.io`, `{subdomain:[a-z]+}.idocker.io`)
    kind: Rule
    services:
    - name: nexus-service
      port: 8082
    middlewares: 
    - name: redirect-https

私服的使用

ingressroute 创建完成后,就是测试对应私服的使用了,直接拿来使用还不行,会提示证书报错,解决方法列表如下:

  • 将对应 CA 证书文件,复制到 /etc/docker/certs.d/idocker.io 目录即可,没有此目录则选择创建它

    1
    2
    3
    4
    5
    
    mkdir -p /etc/docker/certs.d/idocker.io
    scp node1:/data/helm/nexus/certs/ca.crt /etc/docker/certs.d/idocker.io/
    
    mkdir -p /etc/docker/certs.d/qa.idocker.io
    cp /etc/docker/certs.d/idocker.io/ca.crt  /etc/docker/certs.d/qa.idocker.io/
    

    image-20210608160819915

由于,不支持对泛域名的一次认证,如多个子域名时,还需进行一个多次的 copy 操作。

  • 在对应 docker 配置中添加 --insecure-registry 参数,来忽略证书的校验,常见做法是:

    • 将配置项更改添加至 /etc/docker/daemon.json 配置文件中

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      cat /etc/docker/daemon.json 
      {
          "insecure-registries": [
              "idocker.io",
              "dev.idocker.io",
              "qa.idocker.io",
              "prod.idocker.io"
          ]
      }
      
  • 或者是在 /usr/lib/systemd/system/docker.service 启动文件中的, ExecStart 配置项中添加启动参数

    1
    
    ExecStart=/usr/bin/dockerd --insecure-registry idocker.io,dev.idocker.io...
    

参考文档

nexus 其他高级使用说明,请参考 此篇文档

https://doc.traefik.io/traefik/routing/routers/

https://www.jianshu.com/p/52fedb82ef53

Todo