最近看到一篇公众号讲了Nginx-ingress-controller的应用。下面有人评论如何做日志持久化,刚好工作上遇到该问题,整理一个方案,仅供参考。
nginx-ingress-controller的日志
nginx-ingress-controller的日志包括三个部分:
- controller日志: 输出到stdout,通过启动参数中的–log_dir可已配置输出到文件,重定向到文件后会自动轮转,但不会自动清理
- accesslog:输出到stdout,通过nginx-configuration中的字段可以配置输出到哪个文件。输出到文件后不会自动轮转或清理
- errorlog:输出到stderr,配置方式与accesslog类似。
给controller日志落盘
- 给nginx-ingress-controller挂一个hostpath: /data/log/nginx_ingress_controller/ 映射到容器里的/var/log/nginx_ingress_controller/ ,
- 给nginx-ingress-controller配置log-dir和logtostderr参数,将日志重定向到/var/log/nginx_ingress_controller/中。
controller的日志需要做定时清理。由于controller的日志是通过klog(k8s.io/klog)输出的,会进行日志滚动,所以我们通过脚本定时清理一定时间之前的日志文件即可。
给nginx日志落盘
修改configmap: nginx-configuration。配置accesslog和errorlog的输出路径,替换默认的stdout和stderr。输出路径我们可以与controller一致,便于查找。
accesslog和errorlog都只有一个日志文件,我们可以使用logrotate进行日志轮转,将输出到宿主机上的日志进行轮转和清理。配置如:
$ cat /etc/logrotate.d/nginx.log /data/log/nginx_ingress_controller/access.log { su root list rotate 7 daily maxsize 50M copytruncate missingok create 0644 www-data root }
官方提供的模板中,nginx-ingress-controller默认都是以33这个用户登录启动容器的,因此挂载hostpath路径时存在权限问题。我们需要手动在机器上执行chown -R 33:33 /data/log/nginx_ingress_controller.
自动化ops
nginx日志落盘中,第2、3两点均需要人工运维,有什么解决办法吗?
问题的关键是:有什么办法可以在nginx-ingress-controller容器启动之前加一个hook,将宿主机的指定目录执行chown呢?
可以用initContainer。initcontainer必须在containers中的容器运行前运行完毕并成功退出。利用这一k8s特性,我们开发一个docker image,里面只执行如下脚本:
#!/bin/bash logdir=$LOG_DIR userID=$USER_ID echo "try to set dir: $logdir 's group as $userID" chown -R $userID:$userID $logdir
脚本读取一些环境变量, 确认需要修改哪个目录,改成怎样的user group。
将脚本打包成dockerimage, 放在nginx-ingress-controller的deploy yaml中,作为initcontainers。 注意要对该initcontainer配置环境变量和volumeMount.
再说第二点,我们注意到nginx-ingress-controller的基础镜像中就自带了logrotate,那么问题就简单了,我们将写好的logrotate配置文件以configmap的形式挂载到容器中就可以了。
一个deploy yaml如下:
--- apiVersion: v1 kind: Service metadata: name: ingress-nginx namespace: kube-system spec: type: ClusterIP ports: - name: http port: 80 targetPort: 80 protocol: TCP - name: https port: 443 targetPort: 443 protocol: TCP selector: app: ingress-nginx --- apiVersion: v1 kind: Service metadata: name: default-http-backend namespace: kube-system labels: app: default-http-backend spec: ports: - port: 80 targetPort: 8080 selector: app: default-http-backend --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: default namespace: kube-system spec: backend: serviceName: default-http-backend servicePort: 80 --- kind: ConfigMap apiVersion: v1 metadata: name: nginx-configuration namespace: kube-system labels: app: ingress-nginx data: use-forwarded-headers: "true" # 此处配置nginx日志的重定向目标 access-log-path: /var/log/nginx_ingress_controller/access.log error-log-path: /var/log/nginx_ingress_controller/error.log --- # 创建一个configmap,配置nginx日志的轮转策略,对应的是nginx日志在容器内的日志文件 apiVersion: v1 data: nginx.log: | {{ user_nginx_log.host_path }}/access.log { rotate {{ user_nginx_log.rotate_count }} daily maxsize {{ user_nginx_log.rotate_size }} minsize 10M copytruncate missingok create 0644 root root } {{ user_nginx_log.host_path }}/error.log { rotate {{ user_nginx_log.rotate_count }} daily maxsize {{ user_nginx_log.rotate_size }} minsize 10M copytruncate missingok create 0644 root root } kind: ConfigMap metadata: name: nginx-ingress-logrotate namespace: kube-system --- kind: ConfigMap apiVersion: v1 metadata: name: tcp-services namespace: kube-system --- kind: ConfigMap apiVersion: v1 metadata: name: udp-services namespace: kube-system --- apiVersion: v1 kind: ServiceAccount metadata: name: nginx-ingress-serviceaccount namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: nginx-ingress-clusterrole rules: - apiGroups: - "" resources: - configmaps - endpoints - nodes - pods - secrets verbs: - list - watch - apiGroups: - "" resources: - nodes verbs: - get - apiGroups: - "" resources: - services verbs: - get - list - watch - apiGroups: - "extensions" resources: - ingresses verbs: - get - list - watch - apiGroups: - "" resources: - events verbs: - create - patch - apiGroups: - "extensions" resources: - ingresses/status verbs: - update --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: Role metadata: name: nginx-ingress-role namespace: kube-system 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: kube-system roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: nginx-ingress-role subjects: - kind: ServiceAccount name: nginx-ingress-serviceaccount namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: nginx-ingress-clusterrole-nisa-binding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: nginx-ingress-clusterrole subjects: - kind: ServiceAccount name: nginx-ingress-serviceaccount namespace: kube-system --- apiVersion: apps/v1 kind: DaemonSet metadata: name: ingress-nginx namespace: kube-system spec: selector: matchLabels: app: ingress-nginx template: metadata: labels: app: ingress-nginx annotations: prometheus.io/port: '10254' prometheus.io/scrape: 'true' spec: serviceAccountName: nginx-ingress-serviceaccount tolerations: - key: dedicated value: ingress-nginx effect: NoSchedule affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "system/ingress" operator: In values: - "true" dnsPolicy: ClusterFirstWithHostNet hostNetwork: true # 配置initcontainer,确保在nginx-ingress-controller容器启动前将日志目录的权限配置好 initContainers: - name: adddirperm image: "{{ image_registry.addr }}/{{ image.adddirperm }}" env: - name: LOG_DIR value: /var/log/nginx_ingress_controller - name: USER_ID value: "33" volumeMounts: - name: logdir mountPath: /var/log/nginx_ingress_controller containers: - name: nginx-ingress-controller image: "{{ image_registry.addr }}/{{ image.ingress }}" imagePullPolicy: IfNotPresent args: - /nginx-ingress-controller - --default-backend-service=$(POD_NAMESPACE)/default-http-backend - --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 # 设置controller日志的输出路径和方式 - --log_dir=/var/log/nginx_ingress_controller - --logtostderr=false securityContext: 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 resources: requests: cpu: 100m memory: 256Mi livenessProbe: failureThreshold: 3 httpGet: path: /healthz port: 10254 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 readinessProbe: failureThreshold: 3 httpGet: path: /healthz port: 10254 scheme: HTTP periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 volumeMounts: # 配置挂载容器中控制器组件和nginx的日志输出路径 - name: logdir mountPath: /var/log/nginx_ingress_controller # 配置nginx日志的logrotate配置挂载路径 - name: logrotateconf mountPath: /etc/logrotate.d/nginx.log subPath: nginx.log volumes: # 控制器组件和nginx的日志输出路径为宿主机的hostpath - name: logdir hostPath: path: {{ user_nginx_log.host_path }} type: "" # nginx日志的轮转配置文件来自于configmap - name: logrotateconf configMap: name: nginx-ingress-logrotate items: - key: nginx.log path: nginx.log --- apiVersion: apps/v1 kind: DaemonSet metadata: name: default-http-backend namespace: kube-system labels: app: default-http-backend spec: selector: matchLabels: app: default-http-backend template: metadata: labels: app: default-http-backend spec: terminationGracePeriodSeconds: 60 tolerations: - key: dedicated value: ingress-nginx effect: NoSchedule affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "system/ingress" operator: In values: - "true" containers: - name: default-http-backend # Any image is permissible as long as: # 1. It serves a 404 page at / # 2. It serves 200 on a /healthz endpoint image: "{{ image_registry.addr }}/{{ image.http_backend }}" imagePullPolicy: IfNotPresent livenessProbe: httpGet: path: /healthz port: 8080 scheme: HTTP initialDelaySeconds: 30 timeoutSeconds: 5 ports: - containerPort: 8080 resources: limits: cpu: 10m memory: 20Mi requests: cpu: 10m memory: 20Mi ---
最后,有的人建议将initcontainer去掉,改为基于原有的nginx-ingress-controller镜像加一层layer,将配置路径权限的脚本放在该层执行。 个人认为这种方法既不美观,也不方便。唯一的好处仅在于deploy yaml仍然简洁(但少不了volumeMount之类的配置)。不过还是看个人使用感受吧~