实战:ingress-nginx-2022.1.3

实战:ingress-nginx-2022.1.3

image-20220101215429209

目录

实验环境

实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
   k8s version:v1.22.2
   containerd://1.5.5

实验软件

ingress-nginx

我们已经了解了 Ingress 资源对象只是一个路由请求描述配置文件,要让其真正生效还需要对应的 Ingress 控制器才行。Ingress 控制器有很多,这里我们先介绍使用最多的 ingress-nginx它是基于 Nginx 的 Ingress 控制器

1、运行原理

ingress-nginx 控制器主要是用来组装一个 nginx.conf 的配置文件,当配置文件发生任何变动的时候就需要重新加载 Nginx 来生效,但是并不会只在影响 upstream 配置的变更后就重新加载 Nginx,控制器内部会使用一个 lua-nginx-module 来实现该功能。

我们知道 Kubernetes 控制器使用控制循环模式来检查控制器中所需的状态是否已更新或是否需要变更,所以 ingress-nginx 需要使用集群中的不同对象来构建模型,比如 Ingress、Service、Endpoints、Secret、ConfigMap 等可以生成反映集群状态的配置文件的对象,控制器需要一直 Watch 这些资源对象的变化,但是并没有办法知道特定的更改是否会影响到最终生成的 nginx.conf 配置文件,所以一旦 Watch 到了任何变化,控制器都必须根据集群的状态重建一个新的模型,并将其与当前的模型进行比较,如果模型相同则就可以避免生成新的 Nginx 配置并触发重新加载,否则还需要检查模型的差异是否只和端点有关,如果是这样,则然后需要使用 HTTP POST 请求将新的端点列表发送到在 Nginx 内运行的 Lua 处理程序,并再次避免生成新的 Nginx 配置并触发重新加载,如果运行和新模型之间的差异不仅仅是端点,那么就会基于新模型创建一个新的 Nginx 配置了,这样构建模型最大的一个好处就是在状态没有变化时避免不必要的重新加载,可以节省大量 Nginx 重新加载。

下面简单描述了需要重新加载的一些场景:

  • 创建了新的 Ingress 资源
  • TLS 添加到现有 Ingress
  • 从 Ingress 中添加或删除 path 路径
  • Ingress、Service、Secret 被删除了
  • Ingress 的一些缺失引用对象变可用了,例如 Service 或 Secret
  • 更新了一个 Secret

对于集群规模较大的场景下频繁的对Nginx进行重新加载显然会造成大量的性能消耗,所以要尽可能减少出现重新加载的场景。

2、安装

由于 ingress-nginx 所在的节点需要能够访问外网,这样域名可以解析到这些节点上直接使用,所以需要让 ingress-nginx 绑定节点的 80 和 443 端口所以可以使用 hostPort (hostwork也是可以的)来进行访问。当然对于线上环境来说为了保证高可用,一般是需要运行多个ingress-nginx 实例的,然后可以用一个 nginx/haproxy 作为入口,通过 keepalived 来访问边缘节点的 vip 地址。

⚠️ 边缘节点:所谓的边缘节点即集群内部用来向集群外暴露服务能力的节点,集群外部的服务通过该节点来调用集群内部的服务,边缘节点是集群内外交流的一个 Endpoint。

这里我们使用 Helm Chart(后面会详细讲解)的方式来进行安装:

# 如果你不喜欢使用 helm chart 进行安装也可以使用下面的命令`一键安装`
# kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/cloud/deploy.yaml

➜ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
➜ helm repo update
➜ helm fetch ingress-nginx/ingress-nginx
➜ tar -xvf ingress-nginx-4.0.13.tgz && cd ingress-nginx
➜ tree .
.
├── CHANGELOG.md
├── Chart.yaml
├── OWNERS
├── README.md
├── ci
│   ├── controller-custom-ingressclass-flags.yaml
│   ├── daemonset-customconfig-values.yaml
│   ├── daemonset-customnodeport-values.yaml
│   ├── daemonset-headers-values.yaml
│   ├── daemonset-internal-lb-values.yaml
│   ├── daemonset-nodeport-values.yaml
│   ├── daemonset-podannotations-values.yaml
│   ├── daemonset-tcp-udp-configMapNamespace-values.yaml
│   ├── daemonset-tcp-udp-values.yaml
│   ├── daemonset-tcp-values.yaml
│   ├── deamonset-default-values.yaml
│   ├── deamonset-metrics-values.yaml
│   ├── deamonset-psp-values.yaml
│   ├── deamonset-webhook-and-psp-values.yaml
│   ├── deamonset-webhook-values.yaml
│   ├── deployment-autoscaling-behavior-values.yaml
│   ├── deployment-autoscaling-values.yaml
│   ├── deployment-customconfig-values.yaml
│   ├── deployment-customnodeport-values.yaml
│   ├── deployment-default-values.yaml
│   ├── deployment-headers-values.yaml
│   ├── deployment-internal-lb-values.yaml
│   ├── deployment-metrics-values.yaml
│   ├── deployment-nodeport-values.yaml
│   ├── deployment-podannotations-values.yaml
│   ├── deployment-psp-values.yaml
│   ├── deployment-tcp-udp-configMapNamespace-values.yaml
│   ├── deployment-tcp-udp-values.yaml
│   ├── deployment-tcp-values.yaml
│   ├── deployment-webhook-and-psp-values.yaml
│   ├── deployment-webhook-resources-values.yaml
│   └── deployment-webhook-values.yaml
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── _params.tpl
│   ├── admission-webhooks
│   │   ├── job-patch
│   │   │   ├── clusterrole.yaml
│   │   │   ├── clusterrolebinding.yaml
│   │   │   ├── job-createSecret.yaml
│   │   │   ├── job-patchWebhook.yaml
│   │   │   ├── psp.yaml
│   │   │   ├── role.yaml
│   │   │   ├── rolebinding.yaml
│   │   │   └── serviceaccount.yaml
│   │   └── validating-webhook.yaml
│   ├── clusterrole.yaml
│   ├── clusterrolebinding.yaml
│   ├── controller-configmap-addheaders.yaml
│   ├── controller-configmap-proxyheaders.yaml
│   ├── controller-configmap-tcp.yaml
│   ├── controller-configmap-udp.yaml
│   ├── controller-configmap.yaml
│   ├── controller-daemonset.yaml
│   ├── controller-deployment.yaml
│   ├── controller-hpa.yaml
│   ├── controller-ingressclass.yaml
│   ├── controller-keda.yaml
│   ├── controller-poddisruptionbudget.yaml
│   ├── controller-prometheusrules.yaml
│   ├── controller-psp.yaml
│   ├── controller-role.yaml
│   ├── controller-rolebinding.yaml
│   ├── controller-service-internal.yaml
│   ├── controller-service-metrics.yaml
│   ├── controller-service-webhook.yaml
│   ├── controller-service.yaml
│   ├── controller-serviceaccount.yaml
│   ├── controller-servicemonitor.yaml
│   ├── default-backend-deployment.yaml
│   ├── default-backend-hpa.yaml
│   ├── default-backend-poddisruptionbudget.yaml
│   ├── default-backend-psp.yaml
│   ├── default-backend-role.yaml
│   ├── default-backend-rolebinding.yaml
│   ├── default-backend-service.yaml
│   ├── default-backend-serviceaccount.yaml
│   └── dh-param-secret.yaml
└── values.yaml

4 directories, 81 files

Helm Chart 包下载下来后解压就可以看到里面包含的模板文件,其中的 ci 目录中就包含了各种场景下面安装的 Values 配置文件values.yaml 文件中包含的是所有可配置的默认值,我们可以对这些默认值进行覆盖。

我们这里测试环境就将 master1 节点看成边缘节点,所以我们就直接将 ingress-nginx 固定到 master1 节点上,采用 hostNetwork 模式(生产环境可以使用 LB + DaemonSet hostNetwork 模式),为了避免创建的错误 Ingress 等资源对象影响控制器重新加载,所以⚠️我们也强烈建议大家开启准入控制器ingess-nginx 中会提供一个用于校验资源对象的 Admission Webhook,我们可以通过 Values 文件进行开启。

然后新建一个名为 ci/daemonset-prod.yaml 的 Values 文件,用来覆盖 ingress-nginx 默认的 Values 值。

注意:当然你有多个边缘节点的话,可以对这些边缘节点打上一个label,然后把ingress-nginx给固定到上面去,然后采用hostNetwork模式。

⚠️注意:为了避免创建的错误 Ingress 等资源对象影响控制器重新加载,所以我们也强烈建议大家开启准入控制器,ingess-nginx 中会提供一个用于校验资源对象的 Admission Webhook,我们可以通过 Values 文件进行开启。

设置成true之后,当我们去创建Ingress资源对象的时候,它会用这里的admission Webhooks去做一个校验,就是你不是一个正确的Ingress资源对象的话,那它就会拒绝掉。那你拒绝掉之后,我们这个Ingress-controller它就不会去重新加载了,或多或少它可以它可以避免控制器重新加载之类的情况。

[root@master1 ci]#/root/ingress-nginx/ci/deployment-webhook-values.yaml

image-20220102134804177

主机网络模式

对应的 Values 配置文件如下所示:

#注意:以下配置参数不是随便设置的,一定要是values.yaml里面有的才可以的哦;

vim ci/daemonset-prod.yaml

# ci/daemonset-prod.yaml
controller:
  name: controller
  image:
    repository: cnych/ingress-nginx #老师这里是转存过的。
    tag: "v1.1.0"
    digest:

  dnsPolicy: ClusterFirstWithHostNet

  hostNetwork: true

  publishService:  # hostNetwork 模式下设置为false,通过节点IP地址上报ingress status数据
    enabled: false

  # 是否需要处理不带 ingressClass 注解或者 ingressClassName 属性的 Ingress 对象
  # 设置为 true 会在控制器启动参数中新增一个 --watch-ingress-without-class 标注
  watchIngressWithoutClass: false

  kind: DaemonSet

  tolerations:   # kubeadm 安装的集群默认情况下master是有污点,需要容忍这个污点才可以部署
  - key: "node-role.kubernetes.io/master"
    operator: "Equal"
    effect: "NoSchedule"

  nodeSelector:   # 固定到master1节点
    kubernetes.io/hostname: master1

  service:  # HostNetwork 模式不需要创建service
    enabled: false

  admissionWebhooks: # 强烈建议开启 admission webhook
    enabled: true
    createSecretJob:
      resources:
        limits:
          cpu: 10m
          memory: 20Mi
        requests:
          cpu: 10m
          memory: 20Mi
    patchWebhookJob:
      resources:
        limits:
          cpu: 10m
          memory: 20Mi
        requests:
          cpu: 10m
          memory: 20Mi
    patch:
      enabled: true
      image:
        repository: cnych/ingress-nginx-webhook-certgen #老师做了镜像转存
        tag: v1.1.1
        digest:

defaultBackend:  # 配置默认后端
  enabled: true
  name: defaultbackend
  image:
    repository: cnych/ingress-nginx-defaultbackend #老师做了镜像转存
    tag: "1.5"

[root@master1 ingress-nginx]#vim /root/ingress-nginx/values.yaml

image-20220102135302126

📍 注意:部署成deployment和daemonset有什么区别?

假如说你的Ingress想要部署成多个副本,或者说是部署在多个节点上面,那么的话,肯定用daemonset形式。

因为你这个Ingress-nignx只需要一个节点部署一个副本就可以啊,你给他部署多个有什么意义吗?而且我们这里准备使用hostnetwork模式,因为你hostNetwork模式,你一个节点能部署多个副本吗?肯定不行的啦。如果你部署超过一个节点以上,那你就用daemonset模式吧。

这里大家就直接统一用我们的daemonset吧。

image-20220102153145418

📍 注意:我这里是打算只在master1节点上部署ingress-nginx。如果我想在node1和node2个节点行部署ingress-nginx的话,可以给这2个节点打上个label,例如 role=lb,然后只需要把这个label写到 nodeSelector下面就好。

image-20220102154017533

📍 Admission Webhook:这个里面的内容是什么鬼。。。

image-20220102154642141

然后使用如下命令安装 ingress-nginx 应用到 ingress-nginx 的命名空间中:

[root@master1 ci]# kubectl create ns ingress-nginx
namespace/ingress-nginx created
[root@master1 ingress-nginx]#helm upgrade --install ingress-nginx . -f ./ci/daemonset-prod.yaml --namespace ingress-nginx
Release "ingress-nginx" does not exist. Installing it now.
NAME: ingress-nginx
LAST DEPLOYED: Sun Jan  2 15:59:35 2022
NAMESPACE: ingress-nginx
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The ingress-nginx controller has been installed.
It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status by running 'kubectl --namespace ingress-nginx get services -o wide -w ingress-nginx-controller'

An example Ingress that makes use of the controller:
  apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    name: example
    namespace: foo
  spec:
    ingressClassName: nginx
    rules:
      - host: www.example.com
        http:
          paths:
            - backend:
                service:
                  name: exampleService
                  port:
                    number: 80
              path: /
    # This section is only required if TLS is to be enabled for the Ingress
    tls:
      - hosts:
        - www.example.com
        secretName: example-tls

If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided:

  apiVersion: v1
  kind: Secret
  metadata:
    name: example-tls
    namespace: foo
  data:
    tls.crt: <base64 encoded cert>
    tls.key: <base64 encoded key>
  type: kubernetes.io/tls
[root@master1 ingress-nginx]#

image-20220102160118353

部署完成后查看 Pod 的运行状态:

➜ kubectl get svc -n ingress-nginx
NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
ingress-nginx-controller-admission   ClusterIP   10.96.15.99     <none>        443/TCP   11m
ingress-nginx-defaultbackend         ClusterIP   10.97.250.253   <none>        80/TCP    11m
➜ kubectl get pods -n ingress-nginx
NAME                                            READY   STATUS    RESTARTS   AGE
ingress-nginx-controller-5dfdd4659c-9g7c2       1/1     Running   0          11m
ingress-nginx-defaultbackend-84854cd6cb-xb7rv   1/1     Running   0          11m

➜ POD_NAME=$(kubectl get pods -l app.kubernetes.io/name=ingress-nginx -n ingress-nginx -o jsonpath='{.items[0].metadata.name}')
➜ kubectl exec -it $POD_NAME -n ingress-nginx -- /nginx-ingress-controller --version
kubectl logs -f ingress-nginx-controller-5dfdd4659c-9g7c2 -n ingress-nginxW1216 08:51:22.179213       7 client_config.go:615] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
I1216 08:51:22.179525       7 main.go:223] "Creating API client" host="https://10.96.0.1:443"
-------------------------------------------------------------------------------
NGINX Ingress controller
  Release:       v1.1.0
  Build:         cacbee86b6ccc45bde8ffc184521bed3022e7dee
  Repository:    https://github.com/kubernetes/ingress-nginx
  nginx version: nginx/1.19.9

-------------------------------------------------------------------------------

I1216 08:51:22.198221       7 main.go:267] "Running in Kubernetes cluster" major="1" minor="22" git="v1.22.2" state="clean" commit="8b5a19147530eaac9476b0ab82980b4088bbc1b2" platform="linux/amd64"
I1216 08:51:22.200478       7 main.go:86] "Valid default backend" service="ingress-nginx/ingress-nginx-defaultbackend"
I1216 08:51:22.611100       7 main.go:104] "SSL fake certificate created" file="/etc/ingress-controller/ssl/default-fake-certificate.pem"
I1216 08:51:22.627386       7 ssl.go:531] "loading tls certificate" path="/usr/local/certificates/cert" key="/usr/local/certificates/key"
I1216 08:51:22.651187       7 nginx.go:255] "Starting NGINX Ingress controller"

当看到上面的信息证明 ingress-nginx 部署成功了,这里我们安装的是最新版本的控制器。安装完成后会自动创建一个 名为 nginxIngressClass 对象:

[root@master1 ingress]#kubectl get ingressclass
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       4h58m

[root@master1 ingress]#kubectl get ingressclass nginx -oyaml
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  annotations:
    meta.helm.sh/release-name: ingress-nginx
    meta.helm.sh/release-namespace: ingress-nginx
  creationTimestamp: "2022-01-02T08:00:44Z"
  generation: 1
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/version: 1.1.0
    helm.sh/chart: ingress-nginx-4.0.13
  name: nginx
  resourceVersion: "1155600"
  uid: e2be0a67-ee5c-4bcb-9733-fd65b63e3d14
spec:
  controller: k8s.io/ingress-nginx

不过这里我们只提供了一个 controller 属性,如果还需要配置一些额外的参数,则可以在安装的 values 文件中进行配置。

📍 查看过程

# 查看pod和svc
[root@master1 ingress-nginx]#kubectl get po -ningress-nginx
NAME                                            READY   STATUS    RESTARTS   AGE
ingress-nginx-controller-hq8cq                  1/1     Running   0          3m49s
ingress-nginx-defaultbackend-84854cd6cb-zct8c   1/1     Running   0          3m49s
[root@master1 ingress-nginx]#kubectl get svc -ningress-nginx
NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
ingress-nginx-controller-admission   ClusterIP   10.99.58.123    <none>        443/TCP   4m11s
ingress-nginx-defaultbackend         ClusterIP   10.108.217.65   <none>        80/TCP    4m11s

# 查看下ingress-nginx-controller-admission这个svc的详细信息
[root@master1 ingress-nginx]#kubectl describe svc ingress-nginx-controller-admission -ningress-nginx
Name:              ingress-nginx-controller-admission
Namespace:         ingress-nginx
Labels:            app.kubernetes.io/component=controller
                   app.kubernetes.io/instance=ingress-nginx
                   app.kubernetes.io/managed-by=Helm
                   app.kubernetes.io/name=ingress-nginx
                   app.kubernetes.io/version=1.1.0
                   helm.sh/chart=ingress-nginx-4.0.13
Annotations:       meta.helm.sh/release-name: ingress-nginx
                   meta.helm.sh/release-namespace: ingress-nginx
Selector:          app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.99.58.123
IPs:               10.99.58.123
Port:              https-webhook  443/TCP
TargetPort:        webhook/TCP
Endpoints:         172.29.9.51:8443
Session Affinity:  None
Events:            <none>

#查看下ingress-nginx-controller-hq8cq的信息
[root@master1 ingress-nginx]#kubectl get po ingress-nginx-controller-hq8cq -ningress-nginx -oyaml
……
  containers:
  - args: #这里是最终的一个启动参数。
    - /nginx-ingress-controller
    - --default-backend-service=$(POD_NAMESPACE)/ingress-nginx-defaultbackend
    - --election-id=ingress-controller-leader
    - --controller-class=k8s.io/ingress-nginx
    - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
    - --validating-webhook=:8443
    - --validating-webhook-certificate=/usr/local/certificates/cert
    - --validating-webhook-key=/usr/local/certificates/key
……
    ports: #这里使用的是hostnetwork模式
    - containerPort: 80
      hostPort: 80
      name: http
      protocol: TCP
    - containerPort: 443
      hostPort: 443
      name: https
      protocol: TCP
    - containerPort: 8443
      hostPort: 8443
      name: webhook #给webhook使用的
      protocol: TCP
……


#查看下这个ingress-controller pod的日志
[root@master1 ingress-nginx]#kubectl logs ingress-nginx-controller-hq8cq -ningress-nginx
-------------------------------------------------------------------------------
NGINX Ingress controller
  Release:       v1.1.0
  Build:         cacbee86b6ccc45bde8ffc184521bed3022e7dee
  Repository:    https://github.com/kubernetes/ingress-nginx
  nginx version: nginx/1.19.9

-------------------------------------------------------------------------------

W0102 08:01:15.484487       7 client_config.go:615] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
I0102 08:01:15.485077       7 main.go:223] "Creating API client" host="https://10.96.0.1:443"
I0102 08:01:15.573525       7 main.go:267] "Running in Kubernetes cluster" major="1" minor="22" git="v1.22.2" state="clean" commit="8b5a19147530eaac9476b0ab82980b4088bbc1b2" platform="linux/amd64"
I0102 08:01:15.618268       7 main.go:86] "Valid default backend" service="ingress-nginx/ingress-nginx-defaultbackend"
I0102 08:01:16.328944       7 main.go:104] "SSL fake certificate created" file="/etc/ingress-controller/ssl/default-fake-certificate.pem" #fake 相当于是一个自动生成的证书。
I0102 08:01:16.500957       7 ssl.go:531] "loading tls certificate" path="/usr/local/certificates/cert" key="/usr/local/certificates/key"
I0102 08:01:16.537831       7 nginx.go:255] "Starting NGINX Ingress controller"
I0102 08:01:16.702547       7 event.go:282] Event(v1.ObjectReference{Kind:"ConfigMap", Namespace:"ingress-nginx", Name:"ingress-nginx-controller", UID:"3a692b3f-5179-4c10-9d85-c0aea470358e", APIVersion:"v1", ResourceVersion:"1155577", FieldPath:""}): type: 'Normal' reason: 'CREATE' ConfigMap ingress-nginx/ingress-nginx-controller
I0102 08:01:17.847762       7 store.go:420] "Ignoring ingress because of error while validating ingress class" ingress="default/demo-ingress" error="ingress does not contain a valid IngressClass"
I0102 08:01:17.940230       7 nginx.go:297] "Starting NGINX process"
I0102 08:01:17.941600       7 leaderelection.go:248] attempting to acquire leader lease ingress-nginx/ingress-controller-leader...
I0102 08:01:17.942438       7 nginx.go:317] "Starting validation webhook" address=":8443" certPath="/usr/local/certificates/cert" keyPath="/usr/local/certificates/key"
I0102 08:01:17.949407       7 controller.go:155] "Configuration changes detected, backend reload required"
I0102 08:01:17.999391       7 leaderelection.go:258] successfully acquired lease ingress-nginx/ingress-controller-leader
I0102 08:01:18.000439       7 status.go:84] "New leader elected" identity="ingress-nginx-controller-hq8cq"
I0102 08:01:18.116727       7 status.go:215] "POD is not ready" pod="ingress-nginx/ingress-nginx-controller-hq8cq" node="master1"
I0102 08:01:18.813106       7 controller.go:172] "Backend successfully reloaded"
I0102 08:01:18.813225       7 controller.go:183] "Initial sync, sleeping for 1 second"
I0102 08:01:18.813633       7 event.go:282] Event(v1.ObjectReference{Kind:"Pod", Namespace:"ingress-nginx", Name:"ingress-nginx-controller-hq8cq", UID:"400302fc-0586-4e10-8808-19816c0e287d", APIVersion:"v1", ResourceVersion:"1155726", FieldPath:""}): type: 'Normal' reason: 'RELOAD' NGINX reload triggered due to a change in configuration
[root@master1 ingress-nginx]#

#看下ingress-nginx-controller configmap
[root@master1 ingress-nginx]#kubectl get cm ingress-nginx-controller -ningress-nginx -oyaml
# 可以看到,这里configmap的data是空的,如果后续想要对全局的ingress-controller做配置,就可以在这里配置confgimap。
apiVersion: v1
data:
  allow-snippet-annotations: "true"
kind: ConfigMap
metadata:
  annotations:
    meta.helm.sh/release-name: ingress-nginx
    meta.helm.sh/release-namespace: ingress-nginx
  creationTimestamp: "2022-01-02T08:00:44Z"
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/version: 1.1.0
    helm.sh/chart: ingress-nginx-4.0.13
  name: ingress-nginx-controller
  namespace: ingress-nginx
  resourceVersion: "1155577"
  uid: 3a692b3f-5179-4c10-9d85-c0aea470358e
[root@master1 ingress-nginx]#

📍 通过日志我们可以看到一个报错:default/demo-ingress里面没配置IngressClass属性

I0102 08:01:17.847762 7 store.go:420] “Ignoring ingress because of error while validating ingress class” ingress=“default/demo-ingress” error=“ingress does not contain a valid IngressClass”

我们来查看下:

[root@master1 ingress-nginx]#kubectl get ingress
NAME           CLASS    HOSTS   ADDRESS   PORTS   AGE
demo-ingress   <none>   *                 80      24h

[root@master1 ingress-nginx]#kubectl get ingress demo-ingress -oyaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
……
spec: #上面报错,是因为这里没有一个a valid IngressClass。
  rules:
  - http:
      paths:
      - backend:
          service:
            name: test
            port:
              number: 80
        path: /testpath
        pathType: Prefix
status:
  loadBalancer: {}
[root@master1 ingress-nginx]#

我们如何看当前有哪些ingress-controller呢?

[root@master1 ingress-nginx]#kubectl get ingressclass #可以看到这里有一个ingress-class
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       22m

[root@master1 ingress-nginx]#pwd
/root/ingress-nginx
[root@master1 ingress-nginx]#ls
CHANGELOG.md  Chart.yaml  ci  OWNERS  README.md  templates  values.yaml
[root@master1 ingress-nginx]#cat templates/controller-ingressclass.yaml
{{- if .Values.controller.ingressClassResource.enabled -}} #这里是true
# We don't support namespaced ingressClass yet
# So a ClusterRole and a ClusterRoleBinding is required
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  labels:
    {{- include "ingress-nginx.labels" . | nindent 4 }}
    app.kubernetes.io/component: controller
    {{- with .Values.controller.labels }}
    {{- toYaml . | nindent 4 }}
    {{- end }}
  name: {{ .Values.controller.ingressClassResource.name }}
{{- if .Values.controller.ingressClassResource.default }}
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
{{- end }}
spec:
  controller: {{ .Values.controller.ingressClassResource.controllerValue }}
  {{ template "ingressClass.parameters" . }} #这里
{{- end }}


[root@master1 ingress-nginx]#cat values.yaml
  ingressClassResource:
    name: nginx
    enabled: true
    default: false
    controllerValue: "k8s.io/ingress-nginx"

    # Parameters is a link to a custom resource containing additional
    # configuration for the controller. This is optional if the controller
    # does not require extra parameters.
    parameters: {} #这里

如果想让没配置ingressclass的ingress也是用这个ingress-controller,该怎么办呢?

[root@master1 ingress-nginx]#vim templates/controller-ingressclass.yaml

image-20220102165252711

[root@master1 ingress-nginx]#vim values.yaml 要记得把这里的false改为true。

image-20220102165522734

📍 ingress里面的 backend内容

[root@master1 ingress-nginx]#kubectl get ingress demo-ingress -oyaml

image-20220102164148890

📍 注意,生成的这个deafultbackend你可以理解为一个404页面提供的内容。

image-20220102165725362

3、第一个示例

安装成功后,现在我们来为一个 nginx 应用创建一个 Ingress 资源,如下所示:

[root@master1 ingress]#vim first-ingress.yaml

# my-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      app: my-nginx
  template:
    metadata:
      labels:
        app: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    app: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    name: http
  selector:
    app: my-nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-nginx
  namespace: default
spec:
  ingressClassName: nginx  # 使用 nginx 的 IngressClass(关联的 ingress-nginx 控制器)
  rules:
  - host: ngdemo.qikqiak.com  # 将域名映射到 my-nginx 服务
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:  # 将所有请求发送到 my-nginx 服务的 80 端口
            name: my-nginx
            port:
              number: 80
# 不过需要注意大部分Ingress控制器都不是直接转发到Service,而是只是通过Service来获取后端的Endpoints列表(因此这里的svc只起到了一个服务发现的作用),直接转发到Pod,这样可以减少网络跳转,提高性能!!!

⚠️ 注意:

upstream里面可以配置很多的负载策略,并且可以让nginx去帮我们做一个可定制的负载;

image-20220102171045590

直接创建上面的资源对象:

[root@master1 ingress]#kubectl apply -f first-ingress.yaml
deployment.apps/my-nginx created
service/my-nginx created
ingress.networking.k8s.io/my-nginx created
[root@master1 ingress]#kubectl get po
NAME                        READY   STATUS              RESTARTS       AGE
my-nginx-7c4ff94949-lwvjf   1/1     Running    0              8s
nginx-5d59d67564-kgd4q      1/1     Running             0              4d19h
nginx-5d59d67564-lxnt2      1/1     Running             2 (8h ago)     4d19h
test-node-local-dns         1/1     Running             23 (46m ago)   4d20h
[root@master1 ingress]#kubectl get svc
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes      ClusterIP   10.96.0.1        <none>        443/TCP    63d
my-nginx        ClusterIP   10.110.207.185   <none>        80/TCP     18s
nginx-service   ClusterIP   10.106.35.68     <none>        5000/TCP   5d1h

记得在本地pc里配置下域名解析:
C:\WINDOWS\System32\drivers\etc
172.29.9.51 ngdemo.qikqiak.com	

[root@master1 ingress]#kubectl get ingress
NAME           CLASS    HOSTS                ADDRESS       PORTS   AGE
demo-ingress   <none>   *                                  80      31h
my-nginx       nginx    ngdemo.qikqiak.com   172.29.9.51   80      5h10m

在上面的 Ingress 资源对象中我们使用配置 ingressClassName: nginx 指定让我们安装的 ingress-nginx 这个控制器来处理我们的 Ingress 资源,配置的匹配路径类型为前缀的方式去匹配 /,将来自域名 ngdemo.qikqiak.com 的所有请求转发到 my-nginx 服务的后端 Endpoints 中去。

上面资源创建成功后,然后我们可以将域名 ngdemo.qikqiak.com 解析到 ingress-nginx 所在的边缘节点中的任意一个,当然也可以在本地 /etc/hosts 中添加对应的映射也可以,然后就可以通过域名进行访问了。

(本地测试这里直接配置了hosts,但线上的还一般就是用dns了;)

http://ngdemo.qikqiak.com/

image-20220102172051538

下图显示了客户端是如何通过 Ingress 控制器连接到其中一个 Pod 的流程,客户端首先对 ngdemo.qikqiak.com 执行 DNS 解析,得到 Ingress 控制器所在节点的 IP,然后客户端向 Ingress 控制器发送 HTTP 请求,然后根据 Ingress 对象里面的描述匹配域名,找到对应的 Service 对象,并获取关联的 Endpoints 列表,将客户端的请求转发给其中一个 Pod。

ingress controller workflow

前面我们也提到了 ingress-nginx 控制器的核心原理就是将我们的 Ingress 这些资源对象映射翻译成 Nginx 配置文件 nginx.conf,我们可以通过查看控制器中的配置文件来验证这点:

[root@master1 ingress]#kubectl exec ingress-nginx-controller-hq8cq -ningress-nginx -- cat /etc/nginx/nginx.conf

......
upstream upstream_balancer {
        server 0.0.0.1; # placeholder
        balancer_by_lua_block {
                balancer.balance()
        }
        keepalive 320;
        keepalive_timeout  60s;
        keepalive_requests 10000;
}

......
## start server ngdemo.qikqiak.com
server {
        server_name ngdemo.qikqiak.com ;

        listen 80  ;
        listen [::]:80  ;
        listen 443  ssl http2 ;
        listen [::]:443  ssl http2 ;

        set $proxy_upstream_name "-";

        ssl_certificate_by_lua_block {
                certificate.call()
        }

        location / {

                set $namespace      "default";
                set $ingress_name   "my-nginx";
                set $service_name   "my-nginx";
                set $service_port   "80";
                set $location_path  "/";
                set $global_rate_limit_exceeding n;
                ......
                proxy_next_upstream_timeout             0;
                proxy_next_upstream_tries               3;

                proxy_pass http://upstream_balancer #这里

                proxy_redirect                          off;

        }

}
## end server ngdemo.qikqiak.com
......

我们可以在 nginx.conf 配置文件中看到上面我们新增的 Ingress 资源对象的相关配置信息,不过需要注意的是:⚠️现在并不会为每个 backend 后端都创建一个 upstream 配置块,现在是使用 Lua 程序进行动态处理的,所以我们没有直接看到后端的 Endpoints 相关配置数据(以前的版本是可以看到的)。

📍 详细解析过程如下:

image-20220102172334434

image-20220102172537983

hostnetwork模式部署的ingress-controller:

image-20220102172628179

image-20220102172744579

image-20220102173256570

  • 云环境

image-20220102173920443

image-20220102174048926

注意

📍 问题:如何安装helm & 如何是用helm安装ingress-nginx(已解决)?

helm version

image-20220101223638205

那我现在有个问题:

  1. helm如何安装?(好像安装挺简单的,就是装一个二进制文件就好;)
  2. 不用helm安装,用上面的一键安装是否和老师的效果一样呢?这里自己持怀疑态度。。

image-20220101223810232

image-20220101224237631

  • 老师helm安装ingress-nginx步骤

image-20220101224605845

helm repo list

image-20220101224623681

image-20220101224654082

image-20220101224717853

  • helm安装

helm.sh

image-20220101224917057

我们现在都是用v3版本:

helm和kubectl一样,都是去读取的这个kubeconfig文件。

image-20220101225149298

⚠️

也就是说你的kubectl在哪个地方可以使用,那么你的heml就可以在哪个地方使用。

image-20220101225510659

我们把nginx一般就当作流量的入口。

比如说你的客户端client请求到我们的nginx,这里的话,就是ingress-nigx。

客户端一般是通过域名解析(例如这里的www.qikqiak.com),到达这里的ingress-nginx,它为什么能到呢?肯定是你有一个域名解析的,它解析到了我这个ingress-nginx所在的节点,这是最简单的方式;

那如果你是http访问的话,那你肯定是访问80端口的,如果是https的话,肯定是访问443端口的;

所以说,你的nginx要把80和443端口给放开;

当然如果你的域名解析(www.qikqiak.com)有可能不是直接解析到你的nginx这里来的,有可能解析到云上面slb上(那你slb上面肯定要把80|443端口给放开来的),然后这个请求给分发到哪里去呢?那么就看你slb要把请求分发到哪里去呢?可以把它分发到你后面的这个ingress-nginx,比如说分发到她的一个30080| 30443,这样也可以。

所以它的一个安装的场景,是要看你自己的。

我们现在是最简单的,直接通过域名解析到ingress-nginx所在的节点上去的;

image-20220102072005320

  • 注意:helm的2版本和3版本的安装方式应该是有区别的

在helm3之前的版本里,它由客户端helm和服务端tiller组成,而helm3.0之后它去掉了tiller,而直接与k8s通讯,可以说在部署上更简单了。

  • helm基本概念

参考链接:csdn文章 https://blog.csdn.net/weixin_38320674/article/details/106515349 (包含2版本如何安装)

helm安装
Helm相当于linux环境下的yum包管理工具。helm是k8s中的一个命令行客户端工具,helm是tiller的客户端,tiller是一个守护进程,接收helm的请求,helm把请求交给tiller,tiler和apiserver交互,由apiserver负责完成创建,我们用哪个chart需要下载到本地,基于本地这个chart部署实例,这个部署的实例叫做release

1.chart是什么?

一个helm程序包,比方说我们部署nginx,需要deployment的yaml,需要service的yaml,这两个清单文件就是一个helm程序包,在k8s中把这些yaml清单文件叫做chart图表

2.values.yaml文件

values.yaml文件为模板中的文件赋值,可以实现我们自定义安装,如果是chart开发者需要自定义模板,如果是chart使用者只需要修改values.yaml即可。

3.helm可理解如下

helm把kubernetes资源打包到一个chart中,制作并完成各个chart和chart本身依赖关系并利用chart仓库实现对外分发,而helm还可实现可配置的对外发布通过values.yaml文件完成可配置的发布,如果chart版本更新了,helm自动支持滚更更新机制,还可以一键回滚但是不是适合在生产环境使用,除非具有定义自制chart的能力,helm属于kubernetes一个项目,下载地址:

https://github.com/helm/helm/releases

找这个checksum的,解压之后按下面解压即可

helm官方网站:

https://helm.sh/

helm 官方的chart站点:

https://hub.kubeapps.com/

4.repository、release、chart关系

repository:存放chart图表的仓库,提供部署k8s应用程序需要的那些yaml清单文件

release:特定的chart部署于目标集群上的一个实例

chart—>通过values.yaml这个文件赋值–>生成release实例

helm也是go语言开发的。

📍 自己尝试下安装helm(安装成功)

helm安装方法:
当前环境:

k8s v1.22.2(containerd://1.5.5)
helm-v3.7.2
[root@master1 ~]#ll
-rw-r--r-- 1 root root  13870692 Jan  2 08:54 helm-v3.7.2-linux-amd64.tar.gz
[root@master1 ~]#tar zxvf helm-v3.7.2-linux-amd64.tar.gz 
linux-amd64/
linux-amd64/helm
linux-amd64/LICENSE
linux-amd64/README.md
[root@master1 ~]#cd linux-amd64/
[root@master1 linux-amd64]#ls
helm  LICENSE  README.md
[root@master1 linux-amd64]#cp helm /usr/local/bin
[root@master1 linux-amd64]#helm version
version.BuildInfo{Version:"v3.7.2", GitCommit:"663a896f4a815053445eec4153677ddc24a0a361", GitTreeState:"clean", GoVersion:"go1.16.10"}

这个是go开发都 软件包,直接放到PATH路径下就可以使用了!完美。

部署ingress-nginx:

[root@master1 ~]#helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
"ingress-nginx" has been added to your repositories
[root@master1 ~]#helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "ingress-nginx" chart repository
Update Complete. ⎈Happy Helming![root@master1 ~]#helm repo list
NAME         	URL                                   
ingress-nginx	https://kubernetes.github.io/ingress-nginx

[root@master1 ~]#helm fetch ingress-nginx/ingress-nginx
[root@master1 ~]#ll
-rw-r--r-- 1 root root     27766 Jan  2 09:06 ingress-nginx-4.0.13.tgz
[root@master1 ~]#tar -xvf ingress-nginx-4.0.13.tgz && cd ingress-nginx
ingress-nginx/Chart.yaml
ingress-nginx/values.yaml
ingress-nginx/templates/NOTES.txt
ingress-nginx/templates/_helpers.tpl
ingress-nginx/templates/_params.tpl
ingress-nginx/templates/admission-webhooks/job-patch/clusterrole.yaml
ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml
ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml
ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml
ingress-nginx/templates/admission-webhooks/job-patch/psp.yaml
ingress-nginx/templates/admission-webhooks/job-patch/role.yaml
ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml
ingress-nginx/templates/admission-webhooks/job-patch/serviceaccount.yaml
ingress-nginx/templates/admission-webhooks/validating-webhook.yaml
ingress-nginx/templates/clusterrole.yaml
ingress-nginx/templates/clusterrolebinding.yaml
ingress-nginx/templates/controller-configmap-addheaders.yaml
ingress-nginx/templates/controller-configmap-proxyheaders.yaml
ingress-nginx/templates/controller-configmap-tcp.yaml
ingress-nginx/templates/controller-configmap-udp.yaml
ingress-nginx/templates/controller-configmap.yaml
ingress-nginx/templates/controller-daemonset.yaml
ingress-nginx/templates/controller-deployment.yaml
ingress-nginx/templates/controller-hpa.yaml
ingress-nginx/templates/controller-ingressclass.yaml
ingress-nginx/templates/controller-keda.yaml
ingress-nginx/templates/controller-poddisruptionbudget.yaml
ingress-nginx/templates/controller-prometheusrules.yaml
ingress-nginx/templates/controller-psp.yaml
ingress-nginx/templates/controller-role.yaml
ingress-nginx/templates/controller-rolebinding.yaml
ingress-nginx/templates/controller-service-internal.yaml
ingress-nginx/templates/controller-service-metrics.yaml
ingress-nginx/templates/controller-service-webhook.yaml
ingress-nginx/templates/controller-service.yaml
ingress-nginx/templates/controller-serviceaccount.yaml
ingress-nginx/templates/controller-servicemonitor.yaml
ingress-nginx/templates/default-backend-deployment.yaml
ingress-nginx/templates/default-backend-hpa.yaml
ingress-nginx/templates/default-backend-poddisruptionbudget.yaml
ingress-nginx/templates/default-backend-psp.yaml
ingress-nginx/templates/default-backend-role.yaml
ingress-nginx/templates/default-backend-rolebinding.yaml
ingress-nginx/templates/default-backend-service.yaml
ingress-nginx/templates/default-backend-serviceaccount.yaml
ingress-nginx/templates/dh-param-secret.yaml
ingress-nginx/.helmignore
ingress-nginx/CHANGELOG.md
ingress-nginx/OWNERS
ingress-nginx/README.md
ingress-nginx/ci/controller-custom-ingressclass-flags.yaml
ingress-nginx/ci/daemonset-customconfig-values.yaml
ingress-nginx/ci/daemonset-customnodeport-values.yaml
ingress-nginx/ci/daemonset-headers-values.yaml
ingress-nginx/ci/daemonset-internal-lb-values.yaml
ingress-nginx/ci/daemonset-nodeport-values.yaml
ingress-nginx/ci/daemonset-podannotations-values.yaml
ingress-nginx/ci/daemonset-tcp-udp-configMapNamespace-values.yaml
ingress-nginx/ci/daemonset-tcp-udp-values.yaml
ingress-nginx/ci/daemonset-tcp-values.yaml
ingress-nginx/ci/deamonset-default-values.yaml
ingress-nginx/ci/deamonset-metrics-values.yaml
ingress-nginx/ci/deamonset-psp-values.yaml
ingress-nginx/ci/deamonset-webhook-and-psp-values.yaml
ingress-nginx/ci/deamonset-webhook-values.yaml
ingress-nginx/ci/deployment-autoscaling-behavior-values.yaml
ingress-nginx/ci/deployment-autoscaling-values.yaml
ingress-nginx/ci/deployment-customconfig-values.yaml
ingress-nginx/ci/deployment-customnodeport-values.yaml
ingress-nginx/ci/deployment-default-values.yaml
ingress-nginx/ci/deployment-headers-values.yaml
ingress-nginx/ci/deployment-internal-lb-values.yaml
ingress-nginx/ci/deployment-metrics-values.yaml
ingress-nginx/ci/deployment-nodeport-values.yaml
ingress-nginx/ci/deployment-podannotations-values.yaml
ingress-nginx/ci/deployment-psp-values.yaml
ingress-nginx/ci/deployment-tcp-udp-configMapNamespace-values.yaml
ingress-nginx/ci/deployment-tcp-udp-values.yaml
ingress-nginx/ci/deployment-tcp-values.yaml
ingress-nginx/ci/deployment-webhook-and-psp-values.yaml
ingress-nginx/ci/deployment-webhook-resources-values.yaml
ingress-nginx/ci/deployment-webhook-values.yaml
[root@master1 ingress-nginx]#ls
CHANGELOG.md  Chart.yaml  ci  OWNERS  README.md  templates  values.yaml
[root@master1 ingress-nginx]#
  • 我们来看下这个helm下载的ingress-nginx内容
[root@master1 ~]#cd ingress-nginx/
[root@master1 ingress-nginx]#ls
CHANGELOG.md  Chart.yaml  ci  OWNERS  README.md  templates  values.yaml
[root@master1 ingress-nginx]#vim templates/controller-ingressclass.yaml 
#这个其实是go模板渲染的;可能说,大家对这个golang 的template不熟悉,现在让大家去写这个模板,可能有难度。

image-20220102132209892

[root@master1 ingress-nginx]#vim values.yaml

image-20220102132347106

再看一个:

[root@master1 templates]#ls
admission-webhooks                      controller-serviceaccount.yaml
clusterrolebinding.yaml                 controller-service-internal.yaml
clusterrole.yaml                        controller-service-metrics.yaml
controller-configmap-addheaders.yaml    controller-servicemonitor.yaml
controller-configmap-proxyheaders.yaml  controller-service-webhook.yaml
controller-configmap-tcp.yaml           controller-service.yaml
controller-configmap-udp.yaml           default-backend-deployment.yaml
controller-configmap.yaml               default-backend-hpa.yaml
controller-daemonset.yaml               default-backend-poddisruptionbudget.yaml
controller-deployment.yaml              default-backend-psp.yaml
controller-hpa.yaml                     default-backend-rolebinding.yaml
controller-ingressclass.yaml            default-backend-role.yaml
controller-keda.yaml                    default-backend-serviceaccount.yaml
controller-poddisruptionbudget.yaml     default-backend-service.yaml
controller-prometheusrules.yaml         dh-param-secret.yaml
controller-psp.yaml                     _helpers.tpl
controller-rolebinding.yaml             NOTES.txt
controller-role.yaml                    _params.tpl
[root@master1 templates]#vim controller-deployment.yaml

image-20220102133036556

[root@master1 ingress-nginx]#vim values.yaml

image-20220102133102212

  • 再看一个:

其中的 ci 目录中就包含了各种场景下面安装的 Values 配置文件values.yaml 文件中包含的是所有可配置的默认值,我们可以对这些默认值进行覆盖。

[root@master1 ci]#pwd
/root/ingress-nginx/ci
[root@master1 ci]#vim deployment-tcp-values.yam

image-20220102133808696

[root@master1 ingress-nginx]#pwd
/root/ingress-nginx
[root@master1 ingress-nginx]#vim values.yam

image-20220102133908528

📍 学习这部分内容,如果能熟悉nginx知识点,还是非常不错的。

关于我

我的博客主旨:我希望每一个人拿着我的博客都可以做出实验现象,先把实验做出来,然后再结合理论知识更深层次去理解技术点,这样学习起来才有乐趣和动力。并且,我的博客内容步骤是很完整的,也分享源码和实验用到的软件,希望能和大家一起共同进步!

各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人免费帮您解决问题:

  1. 个人微信二维码:x2675263825 (舍得), qq:2675263825。

    image-20211002091450217

  2. 个人博客地址:www.onlyonexl.cn

    image-20211002092057988

  3. 个人微信公众号:云原生架构师实战

    image-20211002141739664

  4. 个人csdn

    https://blog.csdn.net/weixin_39246554?spm=1010.2135.3001.5421

    image-20211002092344616

最后

好了,关于ingress-nginx实验就到这里了,感谢大家阅读,最后贴上我女神的photo,祝大家生活快乐,每天都过的有意义哦,我们下期见!

image-20220102225244382

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值