侧边栏壁纸
  • 累计撰写 51 篇文章
  • 累计创建 23 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

K8s Pod 和 Service 12

zhanjie.me
2020-11-16 / 0 评论 / 0 点赞 / 1 阅读 / 0 字

12.1 Ingress 简介

Ingress 不仅可以让外部客户端访问服务,还可以自定义服务的访问策略。在前面介绍的 LoadBalancer Service 中,我们知道每一个服务都需要定义一个负载均衡器和唯一的共有 IP 地址,如果一个应用可以提供多种服务,那么显然这种方式是不够友好的。而 Ingress 只需要一个公网 IP 地址就可以为多个服务提供自定义的访问。因此,Ingress 是一种非常灵活、得到越来越多厂商支持的集群服务暴露方式,在实际的生产环境中使用得最多,而其他的服务暴露方式更多地适用于服务调试、特殊应用部署等方面。

Ingress 和 Ingress Controller

在 Kubernetes 集群中,Ingress 只是一个统称,具体而言由两部分组成:

  • Ingress:Ingress 资源对象,使用 YAML 文件定义负载均衡的策略(规则)
  • Ingress Controller:控制器,以插件形式存在于集群中,从 apiServer 中获取 Ingress 的规则变化

在集群中,需要先部署 Ingress Controller,再创建 Ingress 资源对象。Ingress Controller 控制器是一个 docker 容器,容器镜像中包含一个负载均衡器(比如:Nginx 或是 HAProxy)和一个 Ingress Controller。

整个底层的实现是这样的:Ingress Controller 从 Kubernetes apiServer 中(实际上是监控 apiServer 的 /ingress 接口后端的 backend services)获取 Ingress 的配置信息,然后动态生成一个 Nginx 或 HAProxy 配置文件,重启负载均衡器进程使配置生效。所以,虽然 Ingress Controller 是以插件形式集成在环境中,但依然是由 Kubernetes 管理的负载均衡器。

Ingress Controller 容器中的负载均衡器可以是各种主流的负载均衡软件,比如:Nginx、HAProxy、Traefik 等。不管具体的软件类型是什么,官方都统称为 Ingress Controller。

当集群使用 Ingress 进行负载分发时,Ingress Controller 基于 Ingress 规则将客户端请求直接转发到 Service 对应的后端 Endpoint(Pod) 上,所以会跳过 kube-proxy 的转发功能,kube-proxy 在这里并不会起作用。这种方式比 LoadBalancer 更加高效。

部署 nginx-ingress-controller

在 Kubernetes 集群中,Ingress Controller 是以 Pod 形式运行的。根据负载均衡器的不同出现了多种 Ingress Controller,比如:

  • Kubernetes Ingress Controller: Kubernetes 官方推荐的控制器,由社区开发,最适合新手。
  • NGINX Ingress Controller: Nginx 公司开发的官方产品。有一个基于 NGINX Plus 的商业版。
  • Traefik: 为部署微服务更加便捷而诞生的现代 HTTP 反向代理、负载均衡工具。支持多种后台来自动化、动态的应用它的配置文件。
  • HAProxy Ingress: 这也是在传统配置时应用非常广泛的负载均衡器。它提供了“软”配置更新(无流量损失)、基于 DNS 的服务发现和通过 API 进行动态配置。

本次实验中将会使用官方推荐的控制器 Kubernetes Ingress Controller,它的创建其实也比较简单,直接使用 YAML 文件创建即可。

需要使用 nginx-ingress-controller.yaml 文件和 ingress-service-nodeport.yaml 文件,文档结尾附内容。

nginx-ingress-controller.yaml 文件中,创建了名为 ingress-nginx 的命名空间(没有使用 kube-system 系统默认空间),并在该命名空间下分别创建了名为 nginx-configuration、tcp-services、udp-services 的 ConfigMap,创建了相关的服务账户、集群角色、集群角色绑定,最后使用 Deployment 部署一个名为 nginx-ingress-controller-xxx 的 Pod 副本,这个 Pod 就是 Ingress Controller。注意在 Deployment.spec.template.spec 中设置 hostNetwork: true,将 Pod 中所有容器的端口号都映射到 Node 节点上,因为是在本机进行测试,直接使用 NodePort:containerPort 就可以直接访问该控制器。同时由于国外的镜像无法下载,这里使用阿里云提供的镜像 registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1,这个容器有两个端口:80 端口提供 http 访问,443 端口提供 https 访问。

nginx-ingress-controller:0.26.1 镜像实现 Ingress Controller 的基本逻辑为:监听 APIServer,获取在 Ingress 中定义的配置信息;基于 Ingress 配置信息,生成 Nginx 所需的配置文件 /etc/nginx/nginx.conf;执行 nginx -s reload 命令重新加载配置文件内容。

ingress-service-nodeport.yaml 文件中,为 Ingress Controller Pod 创建 NodePort 类型的 Service,接收集群外部的流量。

先创建 nginx-ingress-controller Pod:

$ kubectl create -f nginx-ingress-controller.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created

然后为 nginx-ingress-controller Pod 创建 NodePort 类型的 Service:

$ kubectl create -f ingress-service-nodeport.yaml
service/ingress-nginx created

查看 ingress-nginx 命令空间下所有创建好的资源对象:

$ kubectl get all -o wide -n ingress-nginx
NAME                                            READY   STATUS    RESTARTS   AGE   IP             NODE           NOMINATED NODE   READINESS GATES
pod/nginx-ingress-controller-5cd8ff9d6f-ggsd5   1/1     Running   0          85s   192.168.0.33   kubesphere03   <none>           <none>

NAME                    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE   SELECTOR
service/ingress-nginx   NodePort   10.96.141.128   <none>        80:32653/TCP,443:32657/TCP   33s   app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS                 IMAGES                                                                    SELECTOR
deployment.apps/nginx-ingress-controller   1/1     1            1           85s   nginx-ingress-controller   registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1   app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx

NAME                                                  DESIRED   CURRENT   READY   AGE   CONTAINERS                 IMAGES                                                                    SELECTOR
replicaset.apps/nginx-ingress-controller-5cd8ff9d6f   1         1         1       85s   nginx-ingress-controller   registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1   app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx,pod-template-hash=5cd8ff9d6f

部署一个简单的 Nginx 实例

接下来部署一个 Nginx 服务,通过 Ingress 对外暴露 Nginx Service 进行访问,方便大家熟悉 Ingress 的使用流程。

首先使用 Deployment 创建 2 个 Nginx Pod 副本,指定容器端口为 80 端口;然后创建 Nginx Cluster Service,服务端口依然为 80 端口。新建 nginx.yaml 文件并向其中写入如下代码:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: ingress-nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.19
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: ingress-nginx
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80

执行创建:

$ kubectl create -f nginx.yaml
deployment.apps/nginx-deployment created
service/nginx created

设置 ingress 策略,将服务的域名命名为 www.example.com,将客户端对于根路径的访问转发给后端的 nginx Service 进行响应。新建 ingress.yaml 文件,并向其中写入如下代码:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
spec:
  rules:
    - host: www.example.com # 对外访问的域名为 www.example.com
      http:
        paths:
          - path: / # 转发路径为根路径 /
            backend:
              serviceName: nginx # 对外暴露 Service 的名称为 nginx
              servicePort: 80 # nginx Service 的 ClusterPort 为 80

当外部客户端访问地址 http://www.example.com 时,kubernetes 集群内部会将这个访问请求转发给地址 nginx:80。需要注意的是:创建的 Ingress 必须和提供服务的 Service 处于同一命名空间下。

执行创建:

$ kubectl create -f ingress.yaml
ingress.extensions/ingress-nginx created

查看创建的 ingress:

$ kubectl get ingress -n ingress-nginx
NAME            HOSTS             ADDRESS   PORTS   AGE
ingress-nginx   www.example.com             80      41s
# 验证 ingress-nginx CLUSTER-IP
$ kubectl get service -n ingress-nginx
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx   NodePort    10.96.141.128   <none>        80:32653/TCP,443:32657/TCP   43m

可以看到这里的 ADDRESS 为 10.96.141.128,也就是 ingress-nginx Service 的 ClusterIP。这说明 Nginx 设置好了后端服务的 Endpoint,Ingress Controller 中的转发规则已经配置好。如果 ADDRESS 中没有地址,则表示没有配置好。

登录 nginx-ingress-controller Pod,查看 /etc/nginx/nginx.conf 文件是否正确配置转发规则:

$ kubectl exec -n ingress-nginx -it nginx-ingress-controller-5cd8ff9d6f-ggsd5 sh
$ cat nginx.conf
...
    ## start server www.example.com
    server {
        server_name www.example.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      "ingress-nginx";
            set $ingress_name   "ingress-nginx";
            set $service_name   "nginx";
            set $service_port   "80";
            set $location_path  "/";
...

在客户端配置静态解析,公网 IP 是 Ingress Controll Service 的公网 IP(在这里即 NodeIp),域名是 ingress 的 host(在这里即 www.example.com)。

$ echo "192.168.0.33    www.example.com" >> /etc/hosts
$ ping www.example.com
PING www.example.com (192.168.0.33) 56(84) bytes of data.
64 bytes from kubesphere03 (192.168.0.33): icmp_seq=1 ttl=64 time=0.224 ms
64 bytes from kubesphere03 (192.168.0.33): icmp_seq=2 ttl=64 time=0.210 ms
^C
$ curl www.example.com
<!DOCTYPE html>
<html>
...
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
...
</body>
</html>

两个文件内容:

# nginx-ingress-controller.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: tcp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: udp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: nginx-ingress-clusterrole
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses/status
    verbs:
      - update

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: nginx-ingress-role
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-nginx"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: nginx-ingress-role-nisa-binding
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: nginx-ingress-role
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: nginx-ingress-clusterrole-nisa-binding
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: nginx-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        prometheus.io/port: "10254"
        prometheus.io/scrape: "true"
    spec:
      # wait up to five minutes for the drain of connections
      terminationGracePeriodSeconds: 300
      hostNetwork: true
      serviceAccountName: nginx-ingress-serviceaccount
      nodeSelector:
        kubernetes.io/os: linux
      containers:
        - name: nginx-ingress-controller
          image: registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1
          args:
            - /nginx-ingress-controller
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            allowPrivilegeEscalation: true
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 33
            runAsUser: 33
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
            - name: https
              containerPort: 443
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          lifecycle:
            preStop:
              exec:
                command:
                  - /wait-shutdown

---
# ingress-service-nodeport.yaml
kind: Service
apiVersion: v1
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: NodePort
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
  ports:
    - name: http
      port: 80
      targetPort: http
      protocol: TCP
    - name: https
      port: 443
      targetPort: https
      protocol: TCP
  externalTrafficPolicy: Cluster #有两个可用选项:Cluster(默认)和 Local。 Cluster 隐藏了客户端源 IP,可能导致第二跳到另一个节点,但具有良好的整体负载分布。 Local 保留客户端源 IP 并避免 LoadBalancer 和 NodePort 类型服务的第二跳, 但存在潜在的不均衡流量传播风险。

12.2 不同的 Ingress 策略配置类型

对于后端服务的访问规则全部都需要在 Ingress 中进行配置。常用的如下几种:

  • 不使用域名,转发到单个后端服务
  • 不使用域名,不同的 URL 路径被转发到不同的服务
  • 同一域名,不同的 URL 路径被转发到不同的服务
  • 不同的域名,转发到不同的服务

不使用域名,转发到单个后端服务

这种配置适用的场景是:网站不使用域名,直接通过运行 Ingress Controller 的 Node IP 地址访问后端服务。

Ingress Controller 将来自客户端的请求全部转发到后端的单一服务上,比如把对 Ingress Controller 全部的请求都转发到 nginx:80 服务上:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-nginx
spec:
  serviceName: nginx # 对外暴露 Service 的名称为 nginx
  servicePort: 80 # nginx Service 的 ClusterPort 为 80

需要注意的是,使用无域名的 Ingress 转发规则会默认禁用非安全 HTTP,强制使用 HTTPS 访问。

不使用域名,不同的 URL 路径被转发到不同的服务

即使不使用域名,也可以配置对于不同 URL 路径的访问请求转发到不同的服务上。

比如对 /courses 的请求转发到 courses:8080 服务上;对 /louplus 的请求转发到 louplus:8081 服务上:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-shiyanlou
spec:
  rules:
    - http:
        paths:
          - path: /courses
            backend:
              serviceName: courses
              servicePort: 8080
          - path: /louplus
            backend:
              serviceName: louplus
              servicePort: 8081

同一域名,不同的 URL 路径被转发到不同的服务

不过通常情况下,还是推荐使用域名的,这样更加方便外部客户端的访问。使用域名访问网站,通过不同的路径提供不同的服务。

比如对 http://www.example.com/courses 的请求转发到后端 courses:8080 服务上;对 http://www.example.com/vip 的请求转发到后端 vip:2000 服务上:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-example
spec:
  rules:
    - host: www.example.com
      http:
        paths:
          - path: /courses
            backend:
              serviceName: courses
              servicePort: 8080
          - path: /vip
            backend:
              serviceName: vip
              servicePort: 2000

不同的域名,转发到不同的服务

同样也可以配置通过不同的域名提供不同的服务。

比如对 courses.example.com 的请求转发到后端 courses:8080 服务上;对 vip.example.com 的请求转发到后端 vip:2000 的服务上:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-example
spec:
  rules:
    - host: courses.example.com
      http:
        paths:
          - backend:
              serviceName: courses
              servicePort: 8080
    - host: vip.example.com
      http:
        paths:
          - backend:
              serviceName: vip
              servicePort: 2000

12.3 配置 Ingress 处理 TLS 传输

如果想要使用 HTTPS 访问,则需要配置 Ingress 支持 TLS。

比如在 Kubernetes 集群中的 Pod 上运行的是 web 服务器,在集群内部通信使用的是 HTTP 协议。那么只需要 Ingress Controller 处理 HTTPS 连接的相关内容即可。

具体的操作步骤为:

  • 创建自签名的密钥和 SSL 证书文件;
  • 将证书保存到 Kubernetes 中的 Secret 资源对象上;
  • 将 Secret 对象添加到 Ingress 中。

使用 OpenSSL 工具生成密钥和证书文件:

# CN=www.example.com 用于指定网站域名
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout example.com.key -out example.com.crt -subj "/CN=www.example.com"
Generating a 2048 bit RSA private key
.....................................+++
......................+++
writing new private key to 'example.com.key'
-----
$ ls example.com.*
example.com.crt  example.com.key

接下来使用命令 kubectl create secret tls 创建 Secret 资源对象:

$ kubectl create secret tls www.example.com-secret --key example.com.key --cert example.com.crt
secret/www.example.com-secret created

修改前面的 ingress.yaml 文件,将其修改为如下所示:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
spec:
  tls: # 在 tls 字段下进行配置
    - hosts:
        - www.example.com # 域名
      secretName: www.example.com-secret # 对应的 secret
  rules:
    - host: www.example.com # 对外访问的域名为 www.example.com
      http:
        paths:
          - path: / # 转发路径为根路径 /
            backend:
              serviceName: nginx # 对外暴露 Service 的名称为 nginx
              servicePort: 80 # nginx Service 的 ClusterPort 为 80

更新配置信息:

$ kubectl apply -f ingress.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
ingress.extensions/ingress-nginx configured
# 查看更新后的 ingress,可以看到开放了 443 端口
$ kubectl get ingress -n ingress-nginx
NAME            HOSTS             ADDRESS         PORTS     AGE
ingress-nginx   www.example.com   10.96.141.128   80, 443   8m14s

通过浏览器访问 https://www.example.com ,浏览器会提示不安全,继续访问如下:

image-vvwqiiur.png

也可以通过 curl 访问:

$ curl -k https://www.example.com
<!DOCTYPE html>
<html>
...
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
...
</body>
</html>
0

评论区