
[root@ test]# cat app1.go package main import ( "net/http" "log" "fmt" ) func app1(w http.ResponseWriter, r *http.Request){ fmt.Println("app1") w.Write([]byte("app1")) } func app2(w http.ResponseWriter, r *http.Request){ fmt.Println("app2") w.Write([]byte("app2")) } func app3(w http.ResponseWriter, r *http.Request){ fmt.Println("app3") w.Write([]byte("app3")) } func main(){ http.HandleFunc("/app1", app1) http.HandleFunc("/app2", app2) http.HandleFunc("/app3", app3) err := http.ListenAndServe(":5001", nil) if err != nil { log.Fatal("ListenAndServe: ", err.Error()) } } [root@SZD-L0089619 test]# cat Dockerfile From domain/official/centos:7.2.1511 COPY ./app1 /istio-test/app1 CMD ["/istio-test/app1"] [root@ test]# [root@ helloworld]# cat testapp.yaml apiVersion: v1 kind: Service metadata: name: testapp labels: app: testapp spec: type: NodePort ports: - port: 5001 name: http selector: app: testapp --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: testapp-v1 spec: replicas: 1 template: metadata: labels: app: testapp version: v1 spec: containers: - name: testapp image: domain/istio-test:v1 resources: requests: cpu: "100m" imagePullPolicy: IfNotPresent #Always ports: - containerPort: 5001 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: testapp-v2 spec: replicas: 1 template: metadata: labels: app: testapp version: v2 spec: containers: - name: testapp image: domain/istio-test:v2 resources: requests: cpu: "100m" imagePullPolicy: IfNotPresent #Always ports: - containerPort: 5001 --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: testapp annotations: kubernetes.io/ingress.class: "istio" spec: rules: - http: paths: - path: /app1 backend: serviceName: testapp servicePort: 5001 - path: /app2 backend: serviceName: testapp servicePort: 5001 - path: /app3 backend: serviceName: testapp servicePort: 5001 ---
zookeeper和etcd有状态服务部署实践 docker etcd zookeeper kubernetes 4k 次阅读 · 读完需要 78 分钟 0 一. 概述 kubernetes通过statefulset为zookeeper、etcd等这类有状态的应用程序提供完善支持,statefulset具备以下特性: 为pod提供稳定的唯一的网络标识 稳定值持久化存储:通过pv/pvc来实现 启动和停止pod保证有序:优雅的部署和伸缩性 本文阐述了如何在k8s集群上部署zookeeper和etcd有状态服务,并结合ceph实现数据持久化。 二. 总结 使用k8s的statefulset、storageclass、pv、pvc和ceph的rbd,能够很好的支持zookeeper、etcd这样的有状态服务部署到kubernetes集群上。 k8s不会主动删除已经创建的pv、pvc对象,防止出现误删。 如果用户确定删除pv、pvc对象,同时还需要手动删除ceph段的rbd镜像。 遇到的坑 storageclass中引用的ceph客户端用户,必须要有mon rw,rbd rwx权限。如果没有mon write权限,会导致释放rbd锁失败,无法将rbd镜像挂载到其他的k8s worker节点。 zookeeper使用探针检查zookeeper节点的健康状态,如果节点不健康,k8s将删除pod,并自动重建该pod,达到自动重启zookeeper节点的目的。 因zookeeper 3.4版本的集群配置,是通过静态加载文件zoo.cfg来实现的,所以当zookeeper节点pod ip变动后,需要重启zookeeper集群中的所有节点。 etcd部署方式有待优化 本次试验中使用静态方式部署etcd集群,如果etcd节点变迁时,需要执行etcdctl member remove/add等命令手动配置etcd集群,严重限制了etcd集群自动故障恢复、扩容缩容的能力。因此,需要考虑对部署方式优化,改为使用DNS或者etcd descovery的动态方式部署etcd,才能让etcd更好的运行在k8s上。 三. zookeeper集群部署 1. 下载镜像 docker pull gcr.mirrors.ustc.edu.cn/google_containers/kubernetes-zookeeper:1.0-3.4.10 docker tag gcr.mirrors.ustc.edu.cn/google_containers/kubernetes-zookeeper:1.0-3.4.10 172.16.18.100:5000/gcr.io/google_containers/kubernetes-zookeeper:1.0-3.4.10 docker push 172.16.18.100:5000/gcr.io/google_containers/kubernetes-zookeeper:1.0-3.4.10 2. 定义ceph secret cat << EOF | kubectl create -f - apiVersion: v1 data: key: QVFBYy9ndGFRUno4QlJBQXMxTjR3WnlqN29PK3VrMzI1a05aZ3c9PQo= kind: Secret metadata: creationTimestamp: 2017-11-20T10:29:05Z name: ceph-secret namespace: default resourceVersion: "2954730" selfLink: /api/v1/namespaces/default/secrets/ceph-secret uid: a288ff74-cddd-11e7-81cc-000c29f99475 type: kubernetes.io/rbd EOF 3. 定义storageclass rbd存储 cat << EOF | kubectl create -f - apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: ceph parameters: adminId: admin adminSecretName: ceph-secret adminSecretNamespace: default fsType: ext4 imageFormat: "2" imagefeatures: layering monitors: 172.16.13.223 pool: k8s userId: admin userSecretName: ceph-secret provisioner: kubernetes.io/rbd reclaimPolicy: Delete EOF 4. 创建zookeeper集群 使用rbd存储zookeeper节点数据 cat << EOF | kubectl create -f - --- apiVersion: v1 kind: Service metadata: name: zk-hs labels: app: zk spec: ports: - port: 2888 name: server - port: 3888 name: leader-election clusterIP: None selector: app: zk --- apiVersion: v1 kind: Service metadata: name: zk-cs labels: app: zk spec: ports: - port: 2181 name: client selector: app: zk --- apiVersion: policy/v1beta1 kind: PodDisruptionBudget metadata: name: zk-pdb spec: selector: matchLabels: app: zk maxUnavailable: 1 --- apiVersion: apps/v1beta2 # for versions before 1.8.0 use apps/v1beta1 kind: StatefulSet metadata: name: zk spec: selector: matchLabels: app: zk serviceName: zk-hs replicas: 3 updateStrategy: type: RollingUpdate podManagementPolicy: Parallel template: metadata: labels: app: zk spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: "app" operator: In values: - zk topologyKey: "kubernetes.io/hostname" containers: - name: kubernetes-zookeeper imagePullPolicy: Always image: "172.16.18.100:5000/gcr.io/google_containers/kubernetes-zookeeper:1.0-3.4.10" ports: - containerPort: 2181 name: client - containerPort: 2888 name: server - containerPort: 3888 name: leader-election command: - sh - -c - "start-zookeeper \ --servers=3 \ --data_dir=/var/lib/zookeeper/data \ --data_log_dir=/var/lib/zookeeper/data/log \ --conf_dir=/opt/zookeeper/conf \ --client_port=2181 \ --election_port=3888 \ --server_port=2888 \ --tick_time=2000 \ --init_limit=10 \ --sync_limit=5 \ --heap=512M \ --max_client_cnxns=60 \ --snap_retain_count=3 \ --purge_interval=12 \ --max_session_timeout=40000 \ --min_session_timeout=4000 \ --log_level=INFO" readinessProbe: exec: command: - sh - -c - "zookeeper-ready 2181" initialDelaySeconds: 10 timeoutSeconds: 5 livenessProbe: exec: command: - sh - -c - "zookeeper-ready 2181" initialDelaySeconds: 10 timeoutSeconds: 5 volumeMounts: - name: datadir mountPath: /var/lib/zookeeper securityContext: runAsUser: 1000 fsGroup: 1000 volumeClaimTemplates: - metadata: name: datadir annotations: volume.beta.kubernetes.io/storage-class: ceph spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi EOF 查看创建结果 [root@172 zookeeper]# kubectl get no NAME STATUS ROLES AGE VERSION 172.16.20.10 Ready <none> 50m v1.8.2 172.16.20.11 Ready <none> 2h v1.8.2 172.16.20.12 Ready <none> 1h v1.8.2 [root@172 zookeeper]# kubectl get po -owide NAME READY STATUS RESTARTS AGE IP NODE zk-0 1/1 Running 0 8m 192.168.5.162 172.16.20.10 zk-1 1/1 Running 0 1h 192.168.2.146 172.16.20.11 [root@172 zookeeper]# kubectl get pv,pvc NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv/pvc-226cb8f0-d322-11e7-9581-000c29f99475 1Gi RWO Delete Bound default/datadir-zk-0 ceph 1h pv/pvc-22703ece-d322-11e7-9581-000c29f99475 1Gi RWO Delete Bound default/datadir-zk-1 ceph 1h NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc/datadir-zk-0 Bound pvc-226cb8f0-d322-11e7-9581-000c29f99475 1Gi RWO ceph 1h pvc/datadir-zk-1 Bound pvc-22703ece-d322-11e7-9581-000c29f99475 1Gi RWO ceph 1h zk-0 pod的rbd的锁信息为 [root@ceph1 ceph]# rbd lock list kubernetes-dynamic-pvc-227b45e5-d322-11e7-90ab-000c29f99475 -p k8s --user admin There is 1 exclusive lock on this image. Locker ID Address client.24146 kubelet_lock_magic_172.16.20.10 172.16.20.10:0/1606152350 5. 测试pod迁移 尝试将172.16.20.10节点设置为污点,让zk-0 pod自动迁移到172.16.20.12 kubectl cordon 172.16.20.10 [root@172 zookeeper]# kubectl get no NAME STATUS ROLES AGE VERSION 172.16.20.10 Ready,SchedulingDisabled <none> 58m v1.8.2 172.16.20.11 Ready <none> 2h v1.8.2 172.16.20.12 Ready <none> 1h v1.8.2 kubectl delete po zk-0 观察zk-0的迁移过程 [root@172 zookeeper]# kubectl get po -owide -w NAME READY STATUS RESTARTS AGE IP NODE zk-0 1/1 Running 0 14m 192.168.5.162 172.16.20.10 zk-1 1/1 Running 0 1h 192.168.2.146 172.16.20.11 zk-0 1/1 Terminating 0 16m 192.168.5.162 172.16.20.10 zk-0 0/1 Terminating 0 16m <none> 172.16.20.10 zk-0 0/1 Terminating 0 16m <none> 172.16.20.10 zk-0 0/1 Terminating 0 16m <none> 172.16.20.10 zk-0 0/1 Terminating 0 16m <none> 172.16.20.10 zk-0 0/1 Terminating 0 16m <none> 172.16.20.10 zk-0 0/1 Pending 0 0s <none> <none> zk-0 0/1 Pending 0 0s <none> 172.16.20.12 zk-0 0/1 ContainerCreating 0 0s <none> 172.16.20.12 zk-0 0/1 Running 0 3s 192.168.3.4 172.16.20.12 此时zk-0正常迁移到172.16.20.12 再查看rbd的锁定信息 [root@ceph1 ceph]# rbd lock list kubernetes-dynamic-pvc-227b45e5-d322-11e7-90ab-000c29f99475 -p k8s --user admin There is 1 exclusive lock on this image. Locker ID Address client.24146 kubelet_lock_magic_172.16.20.10 172.16.20.10:0/1606152350 [root@ceph1 ceph]# rbd lock list kubernetes-dynamic-pvc-227b45e5-d322-11e7-90ab-000c29f99475 -p k8s --user admin There is 1 exclusive lock on this image. Locker ID Address client.24154 kubelet_lock_magic_172.16.20.12 172.16.20.12:0/3715989358 之前在另外一个ceph集群测试这个zk pod迁移的时候,总是报错无法释放lock,经分析应该是使用的ceph账号没有相应的权限,所以导致释放lock失败。记录的报错信息如下: Nov 27 10:45:55 172 kubelet: W1127 10:45:55.551768 11556 rbd_util.go:471] rbd: no watchers on kubernetes-dynamic-pvc-f35a411e-d317-11e7-90ab-000c29f99475 Nov 27 10:45:55 172 kubelet: I1127 10:45:55.694126 11556 rbd_util.go:181] remove orphaned locker kubelet_lock_magic_172.16.20.12 from client client.171490: err exit status 13, output: 2017-11-27 10:45:55.570483 7fbdbe922d40 -1 did not load config file, using default settings. Nov 27 10:45:55 172 kubelet: 2017-11-27 10:45:55.600816 7fbdbe922d40 -1 Errors while parsing config file! Nov 27 10:45:55 172 kubelet: 2017-11-27 10:45:55.600824 7fbdbe922d40 -1 parse_file: cannot open /etc/ceph/ceph.conf: (2) No such file or directory Nov 27 10:45:55 172 kubelet: 2017-11-27 10:45:55.600825 7fbdbe922d40 -1 parse_file: cannot open ~/.ceph/ceph.conf: (2) No such file or directory Nov 27 10:45:55 172 kubelet: 2017-11-27 10:45:55.600825 7fbdbe922d40 -1 parse_file: cannot open ceph.conf: (2) No such file or directory Nov 27 10:45:55 172 kubelet: 2017-11-27 10:45:55.602492 7fbdbe922d40 -1 Errors while parsing config file! Nov 27 10:45:55 172 kubelet: 2017-11-27 10:45:55.602494 7fbdbe922d40 -1 parse_file: cannot open /etc/ceph/ceph.conf: (2) No such file or directory Nov 27 10:45:55 172 kubelet: 2017-11-27 10:45:55.602495 7fbdbe922d40 -1 parse_file: cannot open ~/.ceph/ceph.conf: (2) No such file or directory Nov 27 10:45:55 172 kubelet: 2017-11-27 10:45:55.602496 7fbdbe922d40 -1 parse_file: cannot open ceph.conf: (2) No such file or directory Nov 27 10:45:55 172 kubelet: 2017-11-27 10:45:55.651594 7fbdbe922d40 -1 auth: unable to find a keyring on /etc/ceph/ceph.client.k8s.keyring,/etc/ceph/ceph.keyring,/etc/ceph/keyring,/etc/ceph/keyring.bin,: (2) No such file or directory Nov 27 10:45:55 172 kubelet: rbd: releasing lock failed: (13) Permission denied Nov 27 10:45:55 172 kubelet: 2017-11-27 10:45:55.682470 7fbdbe922d40 -1 librbd: unable to blacklist client: (13) Permission denied k8s rbd volume的实现代码: if lock { // check if lock is already held for this host by matching lock_id and rbd lock id if strings.Contains(output, lock_id) { // this host already holds the lock, exit glog.V(1).Infof("rbd: lock already held for %s", lock_id) return nil } // clean up orphaned lock if no watcher on the image used, statusErr := util.rbdStatus(&b) if statusErr == nil && !used { re := regexp.MustCompile("client.* " + kubeLockMagic + ".*") locks := re.FindAllStringSubmatch(output, -1) for _, v := range locks { if len(v) > 0 { lockInfo := strings.Split(v[0], " ") if len(lockInfo) > 2 { args := []string{"lock", "remove", b.Image, lockInfo[1], lockInfo[0], "--pool", b.Pool, "--id", b.Id, "-m", mon} args = append(args, secret_opt...) cmd, err = b.exec.Run("rbd", args...) # 执行rbd lock remove命令时返回了错误信息 glog.Infof("remove orphaned locker %s from client %s: err %v, output: %s", lockInfo[1], lockInfo[0], err, string(cmd)) } } } } // hold a lock: rbd lock add args := []string{"lock", "add", b.Image, lock_id, "--pool", b.Pool, "--id", b.Id, "-m", mon} args = append(args, secret_opt...) cmd, err = b.exec.Run("rbd", args...) } 可以看到,rbd lock remove操作被拒绝了,原因是没有权限rbd: releasing lock failed: (13) Permission denied。 6. 测试扩容 zookeeper集群节点数从2个扩为3个。 集群节点数为2时,zoo.cfg的配置中定义了两个实例 zookeeper@zk-0:/opt/zookeeper/conf$ cat zoo.cfg #This file was autogenerated DO NOT EDIT clientPort=2181 dataDir=/var/lib/zookeeper/data dataLogDir=/var/lib/zookeeper/data/log tickTime=2000 initLimit=10 syncLimit=5 maxClientCnxns=60 minSessionTimeout=4000 maxSessionTimeout=40000 autopurge.snapRetainCount=3 autopurge.purgeInteval=12 server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888 server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888 使用kubectl edit statefulset zk命令修改replicas=3,start-zookeeper --servers=3, 此时观察pod的变化 [root@172 zookeeper]# kubectl get po -owide -w NAME READY STATUS RESTARTS AGE IP NODE zk-0 1/1 Running 0 1h 192.168.5.170 172.16.20.10 zk-1 1/1 Running 0 1h 192.168.3.12 172.16.20.12 zk-2 0/1 Pending 0 0s <none> <none> zk-2 0/1 Pending 0 0s <none> 172.16.20.11 zk-2 0/1 ContainerCreating 0 0s <none> 172.16.20.11 zk-2 0/1 Running 0 1s 192.168.2.154 172.16.20.11 zk-2 1/1 Running 0 11s 192.168.2.154 172.16.20.11 zk-1 1/1 Terminating 0 1h 192.168.3.12 172.16.20.12 zk-1 0/1 Terminating 0 1h <none> 172.16.20.12 zk-1 0/1 Terminating 0 1h <none> 172.16.20.12 zk-1 0/1 Terminating 0 1h <none> 172.16.20.12 zk-1 0/1 Terminating 0 1h <none> 172.16.20.12 zk-1 0/1 Pending 0 0s <none> <none> zk-1 0/1 Pending 0 0s <none> 172.16.20.12 zk-1 0/1 ContainerCreating 0 0s <none> 172.16.20.12 zk-1 0/1 Running 0 2s 192.168.3.13 172.16.20.12 zk-1 1/1 Running 0 20s 192.168.3.13 172.16.20.12 zk-0 1/1 Terminating 0 1h 192.168.5.170 172.16.20.10 zk-0 0/1 Terminating 0 1h <none> 172.16.20.10 zk-0 0/1 Terminating 0 1h <none> 172.16.20.10 zk-0 0/1 Terminating 0 1h <none> 172.16.20.10 zk-0 0/1 Terminating 0 1h <none> 172.16.20.10 zk-0 0/1 Pending 0 0s <none> <none> zk-0 0/1 Pending 0 0s <none> 172.16.20.10 zk-0 0/1 ContainerCreating 0 0s <none> 172.16.20.10 zk-0 0/1 Running 0 2s 192.168.5.171 172.16.20.10 zk-0 1/1 Running 0 12s 192.168.5.171 172.16.20.10 可以看到zk-0/zk-1都重启了,这样可以加载新的zoo.cfg配置文件,保证集群正确配置。 新的zoo.cfg配置文件记录了3个实例: [root@172 ~]# kubectl exec zk-0 -- cat /opt/zookeeper/conf/zoo.cfg #This file was autogenerated DO NOT EDIT clientPort=2181 dataDir=/var/lib/zookeeper/data dataLogDir=/var/lib/zookeeper/data/log tickTime=2000 initLimit=10 syncLimit=5 maxClientCnxns=60 minSessionTimeout=4000 maxSessionTimeout=40000 autopurge.snapRetainCount=3 autopurge.purgeInteval=12 server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888 server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888 server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888 7. 测试缩容 缩容的时候,zk集群也自动重启了所有的zk节点,缩容过程如下: [root@172 ~]# kubectl get po -owide -w NAME READY STATUS RESTARTS AGE IP NODE zk-0 1/1 Running 0 5m 192.168.5.171 172.16.20.10 zk-1 1/1 Running 0 6m 192.168.3.13 172.16.20.12 zk-2 1/1 Running 0 7m 192.168.2.154 172.16.20.11 zk-2 1/1 Terminating 0 7m 192.168.2.154 172.16.20.11 zk-1 1/1 Terminating 0 7m 192.168.3.13 172.16.20.12 zk-2 0/1 Terminating 0 8m <none> 172.16.20.11 zk-1 0/1 Terminating 0 7m <none> 172.16.20.12 zk-2 0/1 Terminating 0 8m <none> 172.16.20.11 zk-1 0/1 Terminating 0 7m <none> 172.16.20.12 zk-1 0/1 Terminating 0 7m <none> 172.16.20.12 zk-1 0/1 Terminating 0 7m <none> 172.16.20.12 zk-1 0/1 Pending 0 0s <none> <none> zk-1 0/1 Pending 0 0s <none> 172.16.20.12 zk-1 0/1 ContainerCreating 0 0s <none> 172.16.20.12 zk-1 0/1 Running 0 2s 192.168.3.14 172.16.20.12 zk-2 0/1 Terminating 0 8m <none> 172.16.20.11 zk-2 0/1 Terminating 0 8m <none> 172.16.20.11 zk-1 1/1 Running 0 19s 192.168.3.14 172.16.20.12 zk-0 1/1 Terminating 0 7m 192.168.5.171 172.16.20.10 zk-0 0/1 Terminating 0 7m <none> 172.16.20.10 zk-0 0/1 Terminating 0 7m <none> 172.16.20.10 zk-0 0/1 Terminating 0 7m <none> 172.16.20.10 zk-0 0/1 Pending 0 0s <none> <none> zk-0 0/1 Pending 0 0s <none> 172.16.20.10 zk-0 0/1 ContainerCreating 0 0s <none> 172.16.20.10 zk-0 0/1 Running 0 3s 192.168.5.172 172.16.20.10 zk-0 1/1 Running 0 13s 192.168.5.172 172.16.20.10 四. etcd集群部署 1. 创建etcd集群 cat << EOF | kubectl create -f - apiVersion: v1 kind: Service metadata: name: "etcd" annotations: # Create endpoints also if the related pod isn't ready service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" spec: ports: - port: 2379 name: client - port: 2380 name: peer clusterIP: None selector: component: "etcd" --- apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: "etcd" labels: component: "etcd" spec: serviceName: "etcd" # changing replicas value will require a manual etcdctl member remove/add # command (remove before decreasing and add after increasing) replicas: 3 template: metadata: name: "etcd" labels: component: "etcd" spec: containers: - name: "etcd" image: "172.16.18.100:5000/quay.io/coreos/etcd:v3.2.3" ports: - containerPort: 2379 name: client - containerPort: 2380 name: peer env: - name: CLUSTER_SIZE value: "3" - name: SET_NAME value: "etcd" volumeMounts: - name: data mountPath: /var/run/etcd command: - "/bin/sh" - "-ecx" - | IP=$(hostname -i) for i in $(seq 0 $((${CLUSTER_SIZE} - 1))); do while true; do echo "Waiting for ${SET_NAME}-${i}.${SET_NAME} to come up" ping -W 1 -c 1 ${SET_NAME}-${i}.${SET_NAME}.default.svc.cluster.local > /dev/null && break sleep 1s done done PEERS="" for i in $(seq 0 $((${CLUSTER_SIZE} - 1))); do PEERS="${PEERS}${PEERS:+,}${SET_NAME}-${i}=http://${SET_NAME}-${i}.${SET_NAME}.default.svc.cluster.local:2380" done # start etcd. If cluster is already initialized the `--initial-*` options will be ignored. exec etcd --name ${HOSTNAME} \ --listen-peer-urls http://${IP}:2380 \ --listen-client-urls http://${IP}:2379,http://127.0.0.1:2379 \ --advertise-client-urls http://${HOSTNAME}.${SET_NAME}:2379 \ --initial-advertise-peer-urls http://${HOSTNAME}.${SET_NAME}:2380 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster ${PEERS} \ --initial-cluster-state new \ --data-dir /var/run/etcd/default.etcd ## We are using dynamic pv provisioning using the "standard" storage class so ## this resource can be directly deployed without changes to minikube (since ## minikube defines this class for its minikube hostpath provisioner). In ## production define your own way to use pv claims. volumeClaimTemplates: - metadata: name: data annotations: volume.beta.kubernetes.io/storage-class: ceph spec: accessModes: - "ReadWriteOnce" resources: requests: storage: 1Gi EOF 创建完成之后的po,pv,pvc清单如下: [root@172 etcd]# kubectl get po -owide NAME READY STATUS RESTARTS AGE IP NODE etcd-0 1/1 Running 0 15m 192.168.5.174 172.16.20.10 etcd-1 1/1 Running 0 15m 192.168.3.16 172.16.20.12 etcd-2 1/1 Running 0 5s 192.168.5.176 172.16.20.10 2. 测试缩容 kubectl scale statefulset etcd --replicas=2 [root@172 ~]# kubectl get po -owide -w NAME READY STATUS RESTARTS AGE IP NODE etcd-0 1/1 Running 0 17m 192.168.5.174 172.16.20.10 etcd-1 1/1 Running 0 17m 192.168.3.16 172.16.20.12 etcd-2 1/1 Running 0 1m 192.168.5.176 172.16.20.10 etcd-2 1/1 Terminating 0 1m 192.168.5.176 172.16.20.10 etcd-2 0/1 Terminating 0 1m <none> 172.16.20.10 检查集群健康 kubectl exec etcd-0 -- etcdctl cluster-health failed to check the health of member 42c8b94265b9b79a on http://etcd-2.etcd:2379: Get http://etcd-2.etcd:2379/health: dial tcp: lookup etcd-2.etcd on 10.96.0.10:53: no such host member 42c8b94265b9b79a is unreachable: [http://etcd-2.etcd:2379] are all unreachable member 9869f0647883a00d is healthy: got healthy result from http://etcd-1.etcd:2379 member c799a6ef06bc8c14 is healthy: got healthy result from http://etcd-0.etcd:2379 cluster is healthy 发现缩容后,etcd-2并没有从etcd集群中自动删除,可见这个etcd镜像对自动扩容缩容的支持并不够好。 我们手工删除掉etcd-2 [root@172 etcd]# kubectl exec etcd-0 -- etcdctl member remove 42c8b94265b9b79a Removed member 42c8b94265b9b79a from cluster [root@172 etcd]# kubectl exec etcd-0 -- etcdctl cluster-health member 9869f0647883a00d is healthy: got healthy result from http://etcd-1.etcd:2379 member c799a6ef06bc8c14 is healthy: got healthy result from http://etcd-0.etcd:2379 cluster is healthy 3. 测试扩容 从etcd.yaml的启动脚本中可以看出,扩容时新启动一个etcd pod时参数--initial-cluster-state new,该etcd镜像并不支持动态扩容,可以考虑使用基于dns动态部署etcd集群的方式来修改启动脚本,这样才能支持etcd cluster动态扩容。
session保持 如何在service内部实现session保持呢?当然是在service的yaml里进行设置啦。 在service的yaml的sepc里加入以下代码: sessionAffinity: ClientIP sessionAffinityConfig: clientIP: timeoutSeconds: 10800 这样就开启了session保持。下面的timeoutSeconds指的是session保持的时间,这个时间默认是10800秒,也就是三个小时。 那么原理是啥呢?当不设置session保持时,service向后台pod转发规则是轮询。当设置了session保持之后,k8s会根据访问的ip来把请求转发给他以前访问过的pod,这样session就保持住了。 容器root权限 你一定很奇怪,明明进入容器内,可以看到是root用户啊,为什么还要设置容器的root权限?这是因为容器内虽然看起来是root,但是却没有root的所有功能。当需要修改系统文件时,就不被允许。如果你的app恰好就要去修改系统文件,那么就需要明白如何设置容器的root权限。 想要开启容器的root权限,需要做以下操作: 1.设置kube-apiserver与kubelet的 --allow-privileged=true 这样就允许节点上的容器开启privileged。 怎么看当前是不是为true呢? ps -ef|grep kube 然后仔细看,你就会看到是不是为true。 那么如何设置以上参数呢? 因为kube-apiserver与kubelet都是通过二进制文件直接运行的,所以直接在重启时加入以上参数就行。更简单的是,如果已经设置了systemd启动,那么就去/etc/systemd/system/下找到对应的.service文件,改里面的参数,然后通过systemctl命令直接重启就行。 2.设置包含contariners的yaml文件(如deploy的yaml文件),在containers下添加: securityContext: privileged: true 例如pod的yaml文件: apiVersion: v1 kind: Pod metadata: name: hello-world spec: containers: - name: hello-world-container # The container definition # ... securityContext: privileged: true 这个很好理解,但是切记要把这个与podSecurityContext分开。 podSecurityContext是在pod.spec内的属性,虽然它也写作securityContextsecurityContext是container内的属性podSecurityContext的例子如下: apiVersion: v1 kind: Pod metadata: name: hello-world spec: containers: # specification of the pod’s containers # ... securityContext: fsGroup: 1234 supplementalGroups: [5678] seLinuxOptions: level: "s0:c123,c456" 多端口容器 如果app需要开放两个端口,该怎么办呢? 有两种办法, 第一种是起2个service,每个service开放一个端口 第二种是同一个service开放2个端口 下面分析两种方法。 起两个service 明明可以用一个service搞定,为什么还要起两个service呢?我认为是让service更清晰,一个service负责一种服务。 例如,有个app,同时开发9200与9300端口。9200提供web服务,9300提供api。那么,用两个service,分别命名为app-http与app-api,分别暴露9200与9300端口,分别为nodePort与clusterIP方式,这样层次清晰。 同一个service开2个端口 一般我们只有一个端口的时候,在service的yaml文件: ports: - nodePort: 8482 port: 8080 protocol: TCP targetPort: 8080 而如果你想开两个端口,直接复制粘贴可不行,k8s会提示你必须要加上name。所以,如果要开多端口,要为每个port都指定一个name,如: ports: - name: http nodePort: 8482 port: 8080 protocol: TCP targetPort: 8080
基本原理 完全通过三层网络的路由转发来实现 calico ippool 以calico 3.2版本为准,在calico 3.3中增加了blockSize可变更以及针对namespace单独设置子网的功能参考:https://www.projectcalico.org/calico-ipam-explained-and-enhanced/ calico 不同于flannel不需要为每个node分配子网段,所以只需要考虑pod的数量;例如配置 172.0.0.0/16即总共可运行 2^16=65536个pod。 ippool 分配细节: 默认情况下,当网络中出现第一个容器,calico会为容器分配一段子网(子网掩码/26,例如:172.0.118.0/26),后续出现该节点上的pod都从这个子网中分配ip地址,这样做的好处是能够缩减节点上的路由表的规模,按照这种方式节点上2^6=64个ip地址只需要一个路由表项就行了,而不是为每个ip单独创建一个路由表项。如下为etcd中看到的子网段的值: 注意:当64个主机位都用完之后,会从其他可用的的子网段中取值,所以并不是强制该节点只能运行64个pod ,只是增加了路由表项 Calico 网络的转发细节 容器 A1 的 IP 地址为 172.17.8.2/32,这里注意,不是 /24,而是 /32,将容器 A1 作为一个单点的局域网了。 容器 A1 里面的默认路由,Calico 配置得比较有技巧。 default via 169.254.1.1 dev eth0 169.254.1.1 dev eth0 scope link 这个 IP 地址 169.254.1.1 是默认的网关,但是整个拓扑图中没有一张网卡是这个地址。那如何到达这个地址呢? ARP 本地有缓存,通过 ip neigh 命令可以查看。 169.254.1.1 dev eth0 lladdr ee:ee:ee:ee:ee:ee STALE 找个mac地址为外面 caliefb22cb5e12的mac地址,即到达物理机A路由器: 172.17.8.2 dev veth1 scope link 172.17.8.3 dev veth2 scope link 172.17.9.0/24 via 192.168.100.101 dev eth0 proto bird onlink 为了保证物理机A和物理机B能够相互知道,路由信息,calico使用了bgp协议进行路由发现。 IPIP 如果是跨网段的访问,网络之间存在路由器,比上面的架构更加复杂,如何进行路由发现? 需启用IPIP模式。 使用了 IPIP 模式之后,在物理机 A 上,我们能看到这样的路由表: 172.17.8.2 dev veth1 scope link 172.17.8.3 dev veth2 scope link 172.17.9.0/24 via 192.168.200.101 dev tun0 proto bird onlink 这和原来模式的区别在于,下一跳不再是同一个网段的物理机 B 了,IP 为 192.168.200.101,并且不是从 eth0 跳,而是建立一个隧道的端点 tun0,从这里才是下一跳。 内层源 IP 为 172.17.8.2; 内层目标 IP 为 172.17.9.2; 外层源 IP 为 192.168.100.100; 外层目标 IP 为 192.168.200.101。 将这个包从 eth0 发出去,在物理网络上会使用外层的 IP 进行路由,最终到达物理机 B。在物理机 B 上,tun0 会解封装,将内层的源 IP 和目标 IP 拿出来,转发给相应的容器。
中文:https://github.com/hackstoic/kubernetes_practice 英文版: https://github.com/walidshaari/Kubernetes-Certified-Administrator
一、TLS bootstrapping 简介 Kubernetes 在 1.4 版本(我记着是)推出了 TLS bootstrapping 功能;这个功能主要解决了以下问题: 当集群开启了 TLS 认证后,每个节点的 kubelet 组件都要使用由 apiserver 使用的 CA 签发的有效证书才能与 apiserver 通讯;此时如果节点多起来,为每个节点单独签署证书将是一件非常繁琐的事情;TLS bootstrapping 功能就是让 kubelet 先使用一个预定的低权限用户连接到 apiserver,然后向 apiserver 申请证书,kubelet 的证书由 apiserver 动态签署;在配合 RBAC 授权模型下的工作流程大致如下所示(不完整,下面细说) 二、TLS bootstrapping 相关术语 2.1、kubelet server 在官方 TLS bootstrapping 文档中多次提到过 kubelet server 这个东西;在经过翻阅大量文档以及 TLS bootstrapping 设计文档后得出,kubelet server 指的应该是 kubelet 的 10250 端口; kubelet 组件在工作时,采用主动的查询机制,即定期请求 apiserver 获取自己所应当处理的任务,如哪些 pod 分配到了自己身上,从而去处理这些任务;同时 kubelet 自己还会暴露出两个本身 api 的端口,用于将自己本身的私有 api 暴露出去,这两个端口分别是 10250 与 10255;对于 10250 端口,kubelet 会在其上采用 TLS 加密以提供适当的鉴权功能;对于 10255 端口,kubelet 会以只读形式暴露组件本身的私有 api,并且不做鉴权处理 总结一下,就是说 kubelet 上实际上有两个地方用到证书,一个是用于与 API server 通讯所用到的证书,另一个是 kubelet 的 10250 私有 api 端口需要用到的证书 2.2、CSR 请求类型 kubelet 发起的 CSR 请求都是由 controller manager 来做实际签署的,对于 controller manager 来说,TLS bootstrapping 下 kubelet 发起的 CSR 请求大致分为以下三种 nodeclient: kubelet 以 O=system:nodes 和 CN=system:node:(node name) 形式发起的 CSR 请求 selfnodeclient: kubelet client renew 自己的证书发起的 CSR 请求(与上一个证书就有相同的 O 和 CN) selfnodeserver: kubelet server renew 自己的证书发起的 CSR 请求 大白话加自己测试得出的结果: nodeclient 类型的 CSR 仅在第一次启动时会产生,selfnodeclient 类型的 CSR 请求实际上就是 kubelet renew 自己作为 client 跟 apiserver 通讯时使用的证书产生的,selfnodeserver 类型的 CSR 请求则是 kubelet 首次申请或后续 renew 自己的 10250 api 端口证书时产生的 三、TLS bootstrapping 具体引导过程 3.1、Kubernetes TLS 与 RBAC 认证 在说具体的引导过程之前先谈一下 TLS 和 RBAC,因为这两个事不整明白下面的都不用谈; TLS 作用 众所周知 TLS 的作用就是对通讯加密,防止中间人窃听;同时如果证书不信任的话根本就无法与 apiserver 建立连接,更不用提有没有权限向 apiserver 请求指定内容 RBAC 作用 当 TLS 解决了通讯问题后,那么权限问题就应由 RBAC 解决(可以使用其他权限模型,如 ABAC);RBAC 中规定了一个用户或者用户组(subject)具有请求哪些 api 的权限;在配合 TLS 加密的时候,实际上 apiserver 读取客户端证书的 CN 字段作为用户名,读取 O 字段作为用户组 从以上两点上可以总结出两点: 第一,想要与 apiserver 通讯就必须采用由 apiserver CA 签发的证书,这样才能形成信任关系,建立 TLS 连接;第二,可以通过证书的 CN、O 字段来提供 RBAC 所需的用户与用户组 3.2、kubelet 首次启动流程 看完上面的介绍,不知道有没有人想过,既然 TLS bootstrapping 功能是让 kubelet 组件去 apiserver 申请证书,然后用于连接 apiserver;那么第一次启动时没有证书如何连接 apiserver ? 这个问题实际上可以去查看一下 bootstrap.kubeconfig 和 token.csv 得到答案: 在 apiserver 配置中指定了一个 token.csv 文件,该文件中是一个预设的用户配置;同时该用户的 Token 和 apiserver 的 CA 证书被写入了 kubelet 所使用的 bootstrap.kubeconfig 配置文件中;这样在首次请求时,kubelet 使用 bootstrap.kubeconfig 中的 apiserver CA 证书来与 apiserver 建立 TLS 通讯,使用 bootstrap.kubeconfig 中的用户 Token 来向 apiserver 声明自己的 RBAC 授权身份,如下图所示 在有些用户首次启动时,可能与遇到 kubelet 报 401 无权访问 apiserver 的错误;这是因为在默认情况下,kubelet 通过 bootstrap.kubeconfig 中的预设用户 Token 声明了自己的身份,然后创建 CSR 请求;但是不要忘记这个用户在我们不处理的情况下他没任何权限的,包括创建 CSR 请求;所以需要如下命令创建一个 ClusterRoleBinding,将预设用户 kubelet-bootstrap 与内置的 ClusterRole system:node-bootstrapper 绑定到一起,使其能够发起 CSR 请求 kubectl create clusterrolebinding kubelet-bootstrap \ --clusterrole=system:node-bootstrapper \ --user=kubelet-bootstrap 3.3、手动签发证书 在 kubelet 首次启动后,如果用户 Token 没问题,并且 RBAC 也做了相应的设置,那么此时在集群内应该能看到 kubelet 发起的 CSR 请求 出现 CSR 请求后,可以使用 kubectl 手动签发(允许) kubelet 的证书 当成功签发证书后,目标节点的 kubelet 会将证书写入到 --cert-dir= 选项指定的目录中;注意此时如果不做其他设置应当生成四个文件 而 kubelet 与 apiserver 通讯所使用的证书为 kubelet-client.crt,剩下的 kubelet.crt 将会被用于 kubelet server(10250) 做鉴权使用;注意,此时 kubelet.crt 这个证书是个独立于 apiserver CA 的自签 CA,并且删除后 kubelet 组件会重新生成它 四、TLS bootstrapping 证书自动续期 单独把这部分拿出来写,是因为个人觉得上面已经有点乱了;这部分实际上更复杂,只好单独写一下了,因为这部分涉及的东西比较多,所以也不想草率的几笔带过 4.1、RBAC 授权 首先…首先好几次了…嗯,就是说 kubelet 所发起的 CSR 请求是由 controller manager 签署的;如果想要是实现自动续期,就需要让 controller manager 能够在 kubelet 发起证书请求的时候自动帮助其签署证书;那么 controller manager 不可能对所有的 CSR 证书申请都自动签署,这时候就需要配置 RBAC 规则,保证 controller manager 只对 kubelet 发起的特定 CSR 请求自动批准即可;在 TLS bootstrapping 官方文档中,针对上面 2.2 章节提出的 3 种 CSR 请求分别给出了 3 种对应的 ClusterRole,如下所示 # A ClusterRole which instructs the CSR approver to approve a user requesting # node client credentials. kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: approve-node-client-csr rules: - apiGroups: ["certificates.k8s.io"] resources: ["certificatesigningrequests/nodeclient"] verbs: ["create"] --- # A ClusterRole which instructs the CSR approver to approve a node renewing its # own client credentials. kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: approve-node-client-renewal-csr rules: - apiGroups: ["certificates.k8s.io"] resources: ["certificatesigningrequests/selfnodeclient"] verbs: ["create"] --- # A ClusterRole which instructs the CSR approver to approve a node requesting a # serving cert matching its client cert. kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: approve-node-server-renewal-csr rules: - apiGroups: ["certificates.k8s.io"] resources: ["certificatesigningrequests/selfnodeserver"] verbs: ["create"] RBAC 中 ClusterRole 只是描述或者说定义一种集群范围内的能力,这三个 ClusterRole 在 1.7 之前需要自己手动创建,在 1.8 后 apiserver 会自动创建前两个(1.8 以后名称有改变,自己查看文档);以上三个 ClusterRole 含义如下 approve-node-client-csr: 具有自动批准 nodeclient 类型 CSR 请求的能力 approve-node-client-renewal-csr: 具有自动批准 selfnodeclient 类型 CSR 请求的能力 approve-node-server-renewal-csr: 具有自动批准 selfnodeserver 类型 CSR 请求的能力 所以,如果想要 kubelet 能够自动续期,那么就应当将适当的 ClusterRole 绑定到 kubelet 自动续期时所所采用的用户或者用户组身上 4.2、自动续期下的引导过程 在自动续期下引导过程与单纯的手动批准 CSR 有点差异,具体的引导流程地址如下 kubelet 读取 bootstrap.kubeconfig,使用其 CA 与 Token 向 apiserver 发起第一次 CSR 请求(nodeclient) apiserver 根据 RBAC 规则自动批准首次 CSR 请求(approve-node-client-csr),并下发证书(kubelet-client.crt) kubelet 使用刚刚签发的证书(O=system:nodes, CN=system:node:NODE_NAME)与 apiserver 通讯,并发起申请 10250 server 所使用证书的 CSR 请求 apiserver 根据 RBAC 规则自动批准 kubelet 为其 10250 端口申请的证书(kubelet-server-current.crt) 证书即将到期时,kubelet 自动向 apiserver 发起用于与 apiserver 通讯所用证书的 renew CSR 请求和 renew 本身 10250 端口所用证书的 CSR 请求 apiserver 根据 RBAC 规则自动批准两个证书 kubelet 拿到新证书后关闭所有连接,reload 新证书,以后便一直如此 从以上流程我们可以看出,我们如果要创建 RBAC 规则,则至少能满足四种情况: 自动批准 kubelet 首次用于与 apiserver 通讯证书的 CSR 请求(nodeclient) 自动批准 kubelet 首次用于 10250 端口鉴权的 CSR 请求(实际上这个请求走的也是 selfnodeserver 类型 CSR) 自动批准 kubelet 后续 renew 用于与 apiserver 通讯证书的 CSR 请求(selfnodeclient) 自动批准 kubelet 后续 renew 用于 10250 端口鉴权的 CSR 请求(selfnodeserver) 基于以上四种情况,我们需要创建 3 个 ClusterRoleBinding,创建如下 # 自动批准 kubelet 的首次 CSR 请求(用于与 apiserver 通讯的证书) kubectl create clusterrolebinding node-client-auto-approve-csr --clusterrole=approve-node-client-csr --group=system:bootstrappers # 自动批准 kubelet 后续 renew 用于与 apiserver 通讯证书的 CSR 请求 kubectl create clusterrolebinding node-client-auto-renew-crt --clusterrole=approve-node-client-renewal-csr --group=system:nodes # 自动批准 kubelet 发起的用于 10250 端口鉴权证书的 CSR 请求(包括后续 renew) kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole=approve-node-server-renewal-csr --group=system:nodes 4.3、开启自动续期 在 1.7 后,kubelet 启动时增加 --feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true 选项,则 kubelet 在证书即将到期时会自动发起一个 renew 自己证书的 CSR 请求;同时 controller manager 需要在启动时增加 --feature-gates=RotateKubeletServerCertificate=true 参数,再配合上面创建好的 ClusterRoleBinding,kubelet client 和 kubelet server 证才书会被自动签署; 注意,1.7 版本设置自动续期参数后,新的 renew 请求不会立即开始,而是在证书总有效期的70%~90% 的时间时发起;而且经测试 1.7 版本即使自动签发了证书,kubelet 在不重启的情况下不会重新应用新证书;在 1.8 后 kubelet 组件在增加一个 --rotate-certificates 参数后,kubelet 才会自动重载新证书 4.3、证书过期问题 需要重复强调一个问题是: TLS bootstrapping 时的证书实际是由 kube-controller-manager 组件来签署的,也就是说证书有效期是 kube-controller-manager 组件控制的;所以在 1.7 版本以后(我查文档发现的从1.7开始有) kube-controller-manager 组件提供了一个 --experimental-cluster-signing-duration 参数来设置签署的证书有效时间;默认为 8760h0m0s,将其改为 87600h0m0s 即 10 年后再进行 TLS bootstrapping 签署证书即可。 五、TLS bootstrapping 总结以及详细操作 5.1、主要流程细节 kubelet 首次启动通过加载 bootstrap.kubeconfig 中的用户 Token 和 apiserver CA 证书发起首次 CSR 请求,这个 Token 被预先内置在 apiserver 节点的 token.csv 中,其身份为 kubelet-bootstrap 用户和 system:bootstrappers 用户组;想要首次 CSR 请求能成功(成功指的是不会被 apiserver 401 拒绝),则需要先将 kubelet-bootstrap 用户和 system:node-bootstrapper 内置 ClusterRole 绑定; 对于首次 CSR 请求可以手动批准,也可以将 system:bootstrappers 用户组与 approve-node-client-csr ClusterRole 绑定实现自动批准(1.8 之前这个 ClusterRole 需要手动创建,1.8 后 apiserver 自动创建,并更名为system:certificates.k8s.io:certificatesigningrequests:nodeclient) 默认签署的的证书只有 1 年有效期,如果想要调整证书有效期可以通过设置 kube-controller-manager 的 --experimental-cluster-signing-duration 参数实现,该参数默认值为 8760h0m0s 对于证书自动续签,需要通过协调两个方面实现;第一,想要 kubelet 在证书到期后自动发起续期请求,则需要在 kubelet 启动时增加 --feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true 来实现;第二,想要让 controller manager 自动批准续签的 CSR 请求需要在 controller manager 启动时增加 --feature-gates=RotateKubeletServerCertificate=true 参数,并绑定对应的 RBAC 规则;同时需要注意的是 1.7 版本的 kubelet 自动续签后需要手动重启 kubelet 以使其重新加载新证书,而 1.8 后只需要在 kublet 启动时附带 --rotate-certificates 选项就会自动重新加载新证书 5.2、证书及配置文件作用 token.csv 该文件为一个用户的描述文件,基本格式为 Token,用户名,UID,用户组;这个文件在 apiserver 启动时被 apiserver 加载,然后就相当于在集群内创建了一个这个用户;接下来就可以用 RBAC 给他授权;持有这个用户 Token 的组件访问 apiserver 的时候,apiserver 根据 RBAC 定义的该用户应当具有的权限来处理相应请求 bootstarp.kubeconfig 该文件中内置了 token.csv 中用户的 Token,以及 apiserver CA 证书;kubelet 首次启动会加载此文件,使用 apiserver CA 证书建立与 apiserver 的 TLS 通讯,使用其中的用户 Token 作为身份标识像 apiserver 发起 CSR 请求 kubelet-client.crt 该文件在 kubelet 完成 TLS bootstrapping 后生成,此证书是由 controller manager 签署的,此后 kubelet 将会加载该证书,用于与 apiserver 建立 TLS 通讯,同时使用该证书的 CN 字段作为用户名,O 字段作为用户组向 apiserver 发起其他请求 kubelet.crt 该文件在 kubelet 完成 TLS bootstrapping 后并且没有配置 --feature-gates=RotateKubeletServerCertificate=true 时才会生成;这种情况下该文件为一个独立于 apiserver CA 的自签 CA 证书,有效期为 1 年;被用作 kubelet 10250 api 端口 kubelet-server.crt 该文件在 kubelet 完成 TLS bootstrapping 后并且配置了 --feature-gates=RotateKubeletServerCertificate=true 时才会生成;这种情况下该证书由 apiserver CA 签署,默认有效期同样是 1 年,被用作 kubelet 10250 api 端口鉴权 kubelet-client-current.pem 这是一个软连接文件,当 kubelet 配置了 --feature-gates=RotateKubeletClientCertificate=true选项后,会在证书总有效期的 70%~90% 的时间内发起续期请求,请求被批准后会生成一个 kubelet-client-时间戳.pem;kubelet-client-current.pem 文件则始终软连接到最新的真实证书文件,除首次启动外,kubelet 一直会使用这个证书同 apiserver 通讯 kubelet-server-current.pem 同样是一个软连接文件,当 kubelet 配置了 --feature-gates=RotateKubeletServerCertificate=true 选项后,会在证书总有效期的 70%~90% 的时间内发起续期请求,请求被批准后会生成一个 kubelet-server-时间戳.pem;kubelet-server-current.pem 文件则始终软连接到最新的真实证书文件,该文件将会一直被用于 kubelet 10250 api 端口鉴权 5.3、1.7 TLS bootstrapping 配置 apiserver 预先放置 token.csv,内容样例如下 6df3c701f979cee17732c30958745947,kubelet-bootstrap,10001,"system:bootstrappers" 允许 kubelet-bootstrap 用户创建首次启动的 CSR 请求 kubectl create clusterrolebinding kubelet-bootstrap \ --clusterrole=system:node-bootstrapper \ --user=kubelet-bootstrap 配置 kubelet 自动续期,RotateKubeletClientCertificate 用于自动续期 kubelet 连接 apiserver 所用的证书(kubelet-client-xxxx.pem),RotateKubeletServerCertificate 用于自动续期 kubelet 10250 api 端口所使用的证书(kubelet-server-xxxx.pem) KUBELET_ARGS="--cgroup-driver=cgroupfs \ --cluster-dns=10.254.0.2 \ --resolv-conf=/etc/resolv.conf \ --experimental-bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig \ --feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true \ --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \ --fail-swap-on=false \ --cert-dir=/etc/kubernetes/ssl \ --cluster-domain=cluster.local. \ --hairpin-mode=promiscuous-bridge \ --serialize-image-pulls=false \ --pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0" 配置 controller manager 自动批准相关 CSR 请求,如果不配置 --feature-gates=RotateKubeletServerCertificate=true 参数,则即使配置了相关的 RBAC 规则,也只会自动批准 kubelet client 的 renew 请求 KUBE_CONTROLLER_MANAGER_ARGS="--address=0.0.0.0 \ --service-cluster-ip-range=10.254.0.0/16 \ --feature-gates=RotateKubeletServerCertificate=true \ --cluster-name=kubernetes \ --cluster-signing-cert-file=/etc/kubernetes/ssl/k8s-root-ca.pem \ --cluster-signing-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem \ --service-account-private-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem \ --root-ca-file=/etc/kubernetes/ssl/k8s-root-ca.pem \ --leader-elect=true \ --node-monitor-grace-period=40s \ --node-monitor-period=5s \ --pod-eviction-timeout=5m0s" 创建自动批准相关 CSR 请求的 ClusterRole # A ClusterRole which instructs the CSR approver to approve a user requesting # node client credentials. kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: approve-node-client-csr rules: - apiGroups: ["certificates.k8s.io"] resources: ["certificatesigningrequests/nodeclient"] verbs: ["create"] --- # A ClusterRole which instructs the CSR approver to approve a node renewing its # own client credentials. kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: approve-node-client-renewal-csr rules: - apiGroups: ["certificates.k8s.io"] resources: ["certificatesigningrequests/selfnodeclient"] verbs: ["create"] --- # A ClusterRole which instructs the CSR approver to approve a node requesting a # serving cert matching its client cert. kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: approve-node-server-renewal-csr rules: - apiGroups: ["certificates.k8s.io"] resources: ["certificatesigningrequests/selfnodeserver"] verbs: ["create"] 将 ClusterRole 绑定到适当的用户组,以完成自动批准相关 CSR 请求 # 自动批准 system:bootstrappers 组用户 TLS bootstrapping 首次申请证书的 CSR 请求 kubectl create clusterrolebinding node-client-auto-approve-csr --clusterrole=approve-node-client-csr --group=system:bootstrappers # 自动批准 system:nodes 组用户更新 kubelet 自身与 apiserver 通讯证书的 CSR 请求 kubectl create clusterrolebinding node-client-auto-renew-crt --clusterrole=approve-node-client-renewal-csr --group=system:nodes # 自动批准 system:nodes 组用户更新 kubelet 10250 api 端口证书的 CSR 请求 kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole=approve-node-server-renewal-csr --group=system:nodes 一切就绪后启动 kubelet 组件即可,不过需要注意的是 1.7 版本 kubelet 不会自动重载 renew 的证书,需要自己手动重启 5.4、1.8 TLS bootstrapping 配置 apiserver 预先放置 token.csv,内容样例如下 6df3c701f979cee17732c30958745947,kubelet-bootstrap,10001,"system:bootstrappers" 允许 kubelet-bootstrap 用户创建首次启动的 CSR 请求 kubectl create clusterrolebinding kubelet-bootstrap \ --clusterrole=system:node-bootstrapper \ --user=kubelet-bootstrap 配置 kubelet 自动续期,RotateKubeletClientCertificate 用于自动续期 kubelet 连接 apiserver 所用的证书(kubelet-client-xxxx.pem),RotateKubeletServerCertificate 用于自动续期 kubelet 10250 api 端口所使用的证书(kubelet-server-xxxx.pem),--rotate-certificates 选项使得 kubelet 能够自动重载新证书 KUBELET_ARGS="--cgroup-driver=cgroupfs \ --cluster-dns=10.254.0.2 \ --resolv-conf=/etc/resolv.conf \ --experimental-bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig \ --feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true \ --rotate-certificates \ --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \ --fail-swap-on=false \ --cert-dir=/etc/kubernetes/ssl \ --cluster-domain=cluster.local. \ --hairpin-mode=promiscuous-bridge \ --serialize-image-pulls=false \ --pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0" 配置 controller manager 自动批准相关 CSR 请求,如果不配置 --feature-gates=RotateKubeletServerCertificate=true 参数,则即使配置了相关的 RBAC 规则,也只会自动批准 kubelet client 的 renew 请求 KUBE_CONTROLLER_MANAGER_ARGS="--address=0.0.0.0 \ --service-cluster-ip-range=10.254.0.0/16 \ --cluster-name=kubernetes \ --cluster-signing-cert-file=/etc/kubernetes/ssl/k8s-root-ca.pem \ --cluster-signing-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem \ --service-account-private-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem \ --feature-gates=RotateKubeletServerCertificate=true \ --root-ca-file=/etc/kubernetes/ssl/k8s-root-ca.pem \ --leader-elect=true \ --experimental-cluster-signing-duration 10m0s \ --node-monitor-grace-period=40s \ --node-monitor-period=5s \ --pod-eviction-timeout=5m0s" 创建自动批准相关 CSR 请求的 ClusterRole,相对于 1.7 版本,1.8 的 apiserver 自动创建了前两条 ClusterRole,所以只需要创建一条就行了 # A ClusterRole which instructs the CSR approver to approve a node requesting a # serving cert matching its client cert. kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: system:certificates.k8s.io:certificatesigningrequests:selfnodeserver rules: - apiGroups: ["certificates.k8s.io"] resources: ["certificatesigningrequests/selfnodeserver"] verbs: ["create"] 将 ClusterRole 绑定到适当的用户组,以完成自动批准相关 CSR 请求 # 自动批准 system:bootstrappers 组用户 TLS bootstrapping 首次申请证书的 CSR 请求 kubectl create clusterrolebinding node-client-auto-approve-csr --clusterrole=system:certificates.k8s.io:certificatesigningrequests:nodeclient --group=system:bootstrappers # 自动批准 system:nodes 组用户更新 kubelet 自身与 apiserver 通讯证书的 CSR 请求 kubectl create clusterrolebinding node-client-auto-renew-crt --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeclient --group=system:nodes # 自动批准 system:nodes 组用户更新 kubelet 10250 api 端口证书的 CSR 请求 kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeserver --group=system:nodes
SSD磁盘挂载pvcreate /dev/sdcvgcreate datavg /dev/sdclvcreate -l 100%VG -n datalv01 datavgmkfs.xfs /dev/mapper/datavg-datalv01 mkdir -p /Dockermount /dev/mapper/datavg-datalv01 /Dockerecho "/dev/mapper/datavg-datalv01 /Docker xfs defaults 0 0">>/etc/fstabcat /etc/fstab 数据盘动态扩容:pvcreate /dev/sddvgextend datavgsdc /dev/sddlvextend -l +100%FREE /dev/mapper/datavg-datalv01xfs_growfs /dev/mapper/datavg-datalv01 resize2fs /dev/mapper/VolGroup00-LVroot 云磁盘EBS根目录扩展pvcreate /dev/vdbpvdisplay vgextend VolGroup00 /dev/vdbvgdisplaylvextend -L +310G /dev/mapper/VolGroup00-LVrootlvs resize2fs /dev/mapper/VolGroup00-LVroot # 非centos配置操作方式,忽略 xfs_growfs /dev/mapper/VolGroup00-LVroot 设置keepalived为开机自启动:chkconfig keepalived on 命令行解释说明: 若数据盘要做LVM管理执行下面命令自行挂载 pvcreate /dev/sdb #创建物理卷vgcreate VolGroup01 /dev/sdb #创建卷组 VolGroup01vgchange -a y VolGroup01 #激活卷组VolGroup01lvcreate -l +100%free -n LVdata VolGroup01 # 创建逻辑卷LVdatamkfs.xfs /dev/VolGroup01/LVdata #格式化逻辑卷LVdata,若是CentOS6则使用mkfs.ext4,若是CentOS7,使用mkfs.xfsmkdir -p /data #创建挂载点/dataecho "/dev/VolGroup01/LVdata /data xfs defaults 0 0">>/etc/fstab #将挂载信息写入配置中mount -a #挂载df -h #查看已挂载的盘
Docker容器方式搭建Gogs步骤 一. 准备镜像:(从DockerHub下载) gogs:0.11.53 mysql:5.7 二. 运行容器 **注意卷的挂载以及端口的暴露** example: 1> 运行gog容器, 3000端口是Web页面端口,22是用户ssh方式访问git服务的端口 docker run -d -p 3000:3000 -p 2222:22 -v /root/gogs/gogsdata:/data -v /etc/localtime:/etc/localtime gogs/gogs:0.11.53 2> 运行mysql容器,用于存储gogs数据库,注意设置密码和服务器编码 docker run --privileged --name mysql -p 3306:3306 -v /root/gogs/mysqldata:/var/lib/mysql -v /etc/localtime:/etc/localtime -e MYSQL_ROOT_PASSWORD=Paic1234 -d mysql:5.7 --character-set-server=utf8 三. 登录mysql容器创建gogs数据库 [root@SZD-L0103739 gogs]# docker ps | grep mysql 1d8830a5b3a9 mysql:5.7 "docker-entrypoint..." 13 days ago Up 13 days 0.0.0.0:3306->3306/tcp mysql [root@SZD-L0103739 gogs]# docker exec -it 1d88 bash root@1d8830a5b3a9:/# mysql -h 127.0.0.1 -uroot -pPaic1234 ### 1. 登录Mysql mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 63 Server version: 5.7.22 MySQL Community Server (GPL) Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> set names utf8; #### 2. 设置编码 Query OK, 0 rows affected (0.00 sec) mysql> create database gogs; ### 3. 创建数据库 ERROR 1007 (HY000): Can't create database 'gogs'; database exists mysql> 四. 登录Gog Web界面进行配置:http://ip:3000