015.Kubernetes二進制部署所有節點kubelet

一 部署 kubelet


kubelet 運行在每個 worker 節點上,接收 kube-apiserver 發送的請求,管理 Pod 容器,執行交互式命令,如 exec、run、logs 等。

kubelet 啟動時自動向 kube-apiserver 註冊節點信息,內置的 cadvisor 統計和監控節點的資源使用情況。

為確保安全,部署時關閉了 kubelet 的非安全 http 端口,對請求進行認證和授權,拒絕未授權的訪問(如 apiserver、heapster 的請求)。

1.1 安裝kubelet


提示:k8smaster01節點已下載相應二進制,可直接分發至node節點。

1.2 分發kubelet

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     scp kubernetes/server/bin/kubelet root@${all_ip}:/opt/k8s/bin/
  7     ssh root@${all_ip} "chmod +x /opt/k8s/bin/*"
  8   done


1.3 分發kubeconfig

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_name in ${ALL_NAMES[@]}
  4   do
  5     echo ">>> ${all_name}"
  6 
  7     # 創建 token
  8     export BOOTSTRAP_TOKEN=$(kubeadm token create \
  9       --description kubelet-bootstrap-token \
 10       --groups system:bootstrappers:${all_name} \
 11       --kubeconfig ~/.kube/config)
 12 
 13     # 設置集群參數
 14     kubectl config set-cluster kubernetes \
 15       --certificate-authority=/etc/kubernetes/cert/ca.pem \
 16       --embed-certs=true \
 17       --server=${KUBE_APISERVER} \
 18       --kubeconfig=kubelet-bootstrap-${all_name}.kubeconfig
 19 
 20     # 設置客戶端認證參數
 21     kubectl config set-credentials kubelet-bootstrap \
 22       --token=${BOOTSTRAP_TOKEN} \
 23       --kubeconfig=kubelet-bootstrap-${all_name}.kubeconfig
 24 
 25     # 設置上下文參數
 26     kubectl config set-context default \
 27       --cluster=kubernetes \
 28       --user=kubelet-bootstrap \
 29       --kubeconfig=kubelet-bootstrap-${all_name}.kubeconfig
 30 
 31     # 設置默認上下文
 32     kubectl config use-context default --kubeconfig=kubelet-bootstrap-${all_name}.kubeconfig
 33   done



解釋:

向 kubeconfig 寫入的是 token,bootstrap 結束后 kube-controller-manager 為 kubelet 創建 client 和 server 證書。

token 有效期為 1 天,超期后將不能再被用來 boostrap kubelet,且會被 kube-controller-manager 的 tokencleaner 清理;

kube-apiserver 接收 kubelet 的 bootstrap token 后,將請求的 user 設置為 system:bootstrap:<Token ID>,group 設置為 system:bootstrappers,後續將為這個 group 設置 ClusterRoleBinding。

  1 [root@k8smaster01 work]# kubeadm token list --kubeconfig ~/.kube/config		#查看 kubeadm 為各節點創建的 token
  2 [root@k8smaster01 work]# kubectl get secrets  -n kube-system|grep bootstrap-token	#查看各 token 關聯的 Secret




1.5 分發bootstrap kubeconfig

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_name in ${ALL_NAMES[@]}
  4   do
  5     echo ">>> ${all_name}"
  6     scp kubelet-bootstrap-${all_name}.kubeconfig root@${all_name}:/etc/kubernetes/kubelet-bootstrap.kubeconfig
  7   done


1.6 創建kubelet 參數配置文件


從 v1.10 開始,部分 kubelet 參數需在配置文件中配置,建議創建kubelet配置文件。

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# cat > kubelet-config.yaml.template <<EOF
  4 kind: KubeletConfiguration
  5 apiVersion: kubelet.config.k8s.io/v1beta1
  6 address: "##ALL_IP##"
  7 staticPodPath: ""
  8 syncFrequency: 1m
  9 fileCheckFrequency: 20s
 10 httpCheckFrequency: 20s
 11 staticPodURL: ""
 12 port: 10250
 13 readOnlyPort: 0
 14 rotateCertificates: true
 15 serverTLSBootstrap: true
 16 authentication:
 17   anonymous:
 18     enabled: false
 19   webhook:
 20     enabled: true
 21   x509:
 22     clientCAFile: "/etc/kubernetes/cert/ca.pem"
 23 authorization:
 24   mode: Webhook
 25 registryPullQPS: 0
 26 registryBurst: 20
 27 eventRecordQPS: 0
 28 eventBurst: 20
 29 enableDebuggingHandlers: true
 30 enableContentionProfiling: true
 31 healthzPort: 10248
 32 healthzBindAddress: "##ALL_IP##"
 33 clusterDomain: "${CLUSTER_DNS_DOMAIN}"
 34 clusterDNS:
 35   - "${CLUSTER_DNS_SVC_IP}"
 36 nodeStatusUpdateFrequency: 10s
 37 nodeStatusReportFrequency: 1m
 38 imageMinimumGCAge: 2m
 39 imageGCHighThresholdPercent: 85
 40 imageGCLowThresholdPercent: 80
 41 volumeStatsAggPeriod: 1m
 42 kubeletCgroups: ""
 43 systemCgroups: ""
 44 cgroupRoot: ""
 45 cgroupsPerQOS: true
 46 cgroupDriver: cgroupfs
 47 runtimeRequestTimeout: 10m
 48 hairpinMode: promiscuous-bridge
 49 maxPods: 220
 50 podCIDR: "${CLUSTER_CIDR}"
 51 podPidsLimit: -1
 52 resolvConf: /etc/resolv.conf
 53 maxOpenFiles: 1000000
 54 kubeAPIQPS: 1000
 55 kubeAPIBurst: 2000
 56 serializeImagePulls: false
 57 evictionHard:
 58   memory.available:  "100Mi"
 59 nodefs.available:  "10%"
 60 nodefs.inodesFree: "5%"
 61 imagefs.available: "15%"
 62 evictionSoft: {}
 63 enableControllerAttachDetach: true
 64 failSwapOn: true
 65 containerLogMaxSize: 20Mi
 66 containerLogMaxFiles: 10
 67 systemReserved: {}
 68 kubeReserved: {}
 69 systemReservedCgroup: ""
 70 kubeReservedCgroup: ""
 71 enforceNodeAllocatable: ["pods"]
 72 EOF


1.7 分發kubelet 參數配置文件

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     sed -e "s/##ALL_IP##/${all_ip}/" kubelet-config.yaml.template > kubelet-config-${all_ip}.yaml.template
  7     scp kubelet-config-${all_ip}.yaml.template root@${all_ip}:/etc/kubernetes/kubelet-config.yaml
  8   done


1.8 創建kubelet systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# cat > kubelet.service.template <<EOF
  4 [Unit]
  5 Description=Kubernetes Kubelet
  6 Documentation=https://github.com/GoogleCloudPlatform/kubernetes
  7 After=docker.service
  8 Requires=docker.service
  9 
 10 [Service]
 11 WorkingDirectory=${K8S_DIR}/kubelet
 12 ExecStart=/opt/k8s/bin/kubelet \\
 13   --allow-privileged=true \\
 14   --bootstrap-kubeconfig=/etc/kubernetes/kubelet-bootstrap.kubeconfig \\
 15   --cert-dir=/etc/kubernetes/cert \\
 16   --cni-conf-dir=/etc/cni/net.d \\
 17   --container-runtime=docker \\
 18   --container-runtime-endpoint=unix:///var/run/dockershim.sock \\
 19   --root-dir=${K8S_DIR}/kubelet \\
 20   --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \\
 21   --config=/etc/kubernetes/kubelet-config.yaml \\
 22   --hostname-override=##ALL_NAME## \\
 23   --pod-infra-container-image=registry.cn-beijing.aliyuncs.com/k8s_images/pause-amd64:3.1 \\
 24   --image-pull-progress-deadline=15m \\
 25   --volume-plugin-dir=${K8S_DIR}/kubelet/kubelet-plugins/volume/exec/ \\
 26   --logtostderr=true \\
 27   --v=2
 28 Restart=always
 29 RestartSec=5
 30 StartLimitInterval=0
 31 
 32 [Install]
 33 WantedBy=multi-user.target
 34 EOF



解釋:

  • 如果設置了 –hostname-override 選項,則 kube-proxy 也需要設置該選項,否則會出現找不到 Node 的情況;
  • –bootstrap-kubeconfig:指向 bootstrap kubeconfig 文件,kubelet 使用該文件中的用戶名和 token 向 kube-apiserver 發送 TLS Bootstrapping 請求;
  • K8S approve kubelet 的 csr 請求后,在 –cert-dir 目錄創建證書和私鑰文件,然後寫入 –kubeconfig 文件;
  • –pod-infra-container-image 不使用 redhat 的 pod-infrastructure:latest 鏡像,它不能回收容器的殭屍。

1.9 分發kubelet systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_name in ${ALL_NAMES[@]}
  4   do
  5     echo ">>> ${all_name}"
  6     sed -e "s/##ALL_NAME##/${all_name}/" kubelet.service.template > kubelet-${all_name}.service
  7     scp kubelet-${all_name}.service root@${all_name}:/etc/systemd/system/kubelet.service
  8   done


二 啟動驗證

2.1 授權


kubelet 啟動時查找 –kubeletconfig 參數對應的文件是否存在,如果不存在則使用 –bootstrap-kubeconfig 指定的 kubeconfig 文件向 kube-apiserver 發送證書籤名請求 (CSR)。

kube-apiserver 收到 CSR 請求后,對其中的 Token 進行認證,認證通過後將請求的 user 設置為 system:bootstrap:<Token ID>,group 設置為 system:bootstrappers,這一過程稱為 Bootstrap Token Auth。

默認情況下,這個 user 和 group 沒有創建 CSR 的權限,因此kubelet 會啟動失敗,可通過如下方式創建一個 clusterrolebinding,將 group system:bootstrappers 和 clusterrole system:node-bootstrapper 綁定。



  1 [root@k8smaster01 ~]#  kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers

2.2 啟動kubelet

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for all_name in ${ALL_NAMES[@]}
  3   do
  4     echo ">>> ${all_name}"
  5     ssh root@${all_name} "mkdir -p ${K8S_DIR}/kubelet/kubelet-plugins/volume/exec/"
  6     ssh root@${all_name} "/usr/sbin/swapoff -a"
  7     ssh root@${all_name} "systemctl daemon-reload && systemctl enable kubelet && systemctl restart kubelet"
  8   done



kubelet 啟動后使用 –bootstrap-kubeconfig 向 kube-apiserver 發送 CSR 請求,當這個 CSR 被 approve 后,kube-controller-manager 為 kubelet 創建 TLS 客戶端證書、私鑰和 –kubeletconfig 文件。

注意:kube-controller-manager 需要配置 –cluster-signing-cert-file 和 –cluster-signing-key-file 參數,才會為 TLS Bootstrap 創建證書和私鑰。

提示:

啟動服務前必須先創建工作目錄;

關閉 swap 分區,否則 kubelet 會啟動失敗。

2.3 查看kubelet服務

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for all_name in ${ALL_NAMES[@]}
  3   do
  4     echo ">>> ${all_name}"
  5     ssh root@${all_name} "systemctl status kubelet"
  6   done
  7 [root@k8snode01 ~]# kubectl get csr
  8 [root@k8snode01 ~]# kubectl get nodes



三 approve CSR 請求

3.1 自動 approve CSR 請求


創建三個 ClusterRoleBinding,分別用於自動 approve client、renew client、renew server 證書。

  1 [root@k8snode01 ~]# cd /opt/k8s/work
  2 [root@k8snode01 work]# cat > csr-crb.yaml <<EOF
  3  # Approve all CSRs for the group "system:bootstrappers"
  4  kind: ClusterRoleBinding
  5  apiVersion: rbac.authorization.k8s.io/v1
  6  metadata:
  7    name: auto-approve-csrs-for-group
  8  subjects:
  9  - kind: Group
 10    name: system:bootstrappers
 11    apiGroup: rbac.authorization.k8s.io
 12  roleRef:
 13    kind: ClusterRole
 14    name: system:certificates.k8s.io:certificatesigningrequests:nodeclient
 15    apiGroup: rbac.authorization.k8s.io
 16 ---
 17  # To let a node of the group "system:nodes" renew its own credentials
 18  kind: ClusterRoleBinding
 19  apiVersion: rbac.authorization.k8s.io/v1
 20  metadata:
 21    name: node-client-cert-renewal
 22  subjects:
 23  - kind: Group
 24    name: system:nodes
 25    apiGroup: rbac.authorization.k8s.io
 26  roleRef:
 27    kind: ClusterRole
 28    name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
 29    apiGroup: rbac.authorization.k8s.io
 30 ---
 31 # A ClusterRole which instructs the CSR approver to approve a node requesting a
 32 # serving cert matching its client cert.
 33 kind: ClusterRole
 34 apiVersion: rbac.authorization.k8s.io/v1
 35 metadata:
 36   name: approve-node-server-renewal-csr
 37 rules:
 38 - apiGroups: ["certificates.k8s.io"]
 39   resources: ["certificatesigningrequests/selfnodeserver"]
 40   verbs: ["create"]
 41 ---
 42  # To let a node of the group "system:nodes" renew its own server credentials
 43  kind: ClusterRoleBinding
 44  apiVersion: rbac.authorization.k8s.io/v1
 45  metadata:
 46    name: node-server-cert-renewal
 47  subjects:
 48  - kind: Group
 49    name: system:nodes
 50    apiGroup: rbac.authorization.k8s.io
 51  roleRef:
 52    kind: ClusterRole
 53    name: approve-node-server-renewal-csr
 54    apiGroup: rbac.authorization.k8s.io
 55 EOF
 56 [root@k8snode01 work]# kubectl apply -f csr-crb.yaml



解釋:

auto-approve-csrs-for-group:自動 approve node 的第一次 CSR; 注意第一次 CSR 時,請求的 Group 為 system:bootstrappers;

node-client-cert-renewal:自動 approve node 後續過期的 client 證書,自動生成的證書 Group 為 system:nodes;

node-server-cert-renewal:自動 approve node 後續過期的 server 證書,自動生成的證書 Group 為 system:nodes。

3.2 查看 kubelet 的情況

  1 [root@k8snode01 ~]# kubectl get csr | grep boot		#等待一段時間(1-10 分鐘),三個節點的 CSR 都被自動 approved
  2 [root@k8snode01 ~]# kubectl get nodes			#所有節點均 ready
  3 [root@k8snode01 ~]# ls -l /etc/kubernetes/kubelet.kubeconfig
  4 [root@k8snode01 ~]# ls -l /etc/kubernetes/cert/|grep kubelet



3.3 手動 approve server cert csr


基於安全性考慮,CSR approving controllers 不會自動 approve kubelet server 證書籤名請求,需要手動 approve。

  1 [root@k8smaster01 ~]# kubectl get csr
  2 [root@k8smaster01 ~]# kubectl certificate approve csr-2kmtj
  3 



  1 [root@k8smaster01 ~]# ls -l /etc/kubernetes/cert/kubelet-*



四 kubelet API 接口

4.1 kubelet 提供的 API 接口

  1 [root@k8smaster01 ~]# sudo netstat -lnpt|grep kubelet			#查看kubelet監聽端口



解釋:

  • 10248: healthz http 服務;
  • 10250: https 服務,訪問該端口時需要認證和授權(即使訪問 /healthz 也需要);
  • 未開啟只讀端口 10255;
  • 從 K8S v1.10 開始,去除了 –cadvisor-port 參數(默認 4194 端口),不支持訪問 cAdvisor UI & API。

4.2 kubelet api 認證和授權


kubelet 配置了如下認證參數:

  • authentication.anonymous.enabled:設置為 false,不允許匿名�訪問 10250 端口;
  • authentication.x509.clientCAFile:指定簽名客戶端證書的 CA 證書,開啟 HTTPs 證書認證;
  • authentication.webhook.enabled=true:開啟 HTTPs bearer token 認證。


同時配置了如下授權參數:

authroization.mode=Webhook:開啟 RBAC 授權。




kubelet 收到請求后,使用 clientCAFile 對證書籤名進行認證,或者查詢 bearer token 是否有效。如果兩者都沒通過,則拒絕請求,提示 Unauthorized。

  1 [root@k8smaster01 ~]# curl -s --cacert /etc/kubernetes/cert/ca.pem https://172.24.8.71:10250/metrics   
  2 Unauthorized[root@k8smaster01 ~]#
  3 [root@k8smaster01 ~]# curl -s --cacert /etc/kubernetes/cert/ca.pem -H "Authorization: Bearer 123456" https://172.24.8.71:10250/metrics   
  4 Unauthorized



若通過認證后,kubelet 使用 SubjectAccessReview API 向 kube-apiserver 發送請求,查詢證書或 token 對應的 user、group 是否有操作資源的權限(RBAC)。

4.3 證書認證和授權

  1 [root@k8smaster01 ~]# curl -s --cacert /etc/kubernetes/cert/ca.pem --cert /etc/kubernetes/cert/kube-controller-manager.pem --key /etc/kubernetes/cert/kube-controller-manager-key.pem https://172.24.8.71:10250/metrics	#默認權限不足
  2 Forbidden (user=system:kube-controller-manager, verb=get, resource=nodes, subresource=metrics)
  3 curl -s --cacert /etc/kubernetes/cert/ca.pem --cert /opt/k8s/work/admin.pem --key /opt/k8s/work/admin-key.pem https://172.24.8.71:10250/metrics|head				#使用最高權限的admin





解釋:

–cacert、–cert、–key 的參數值必須是文件路徑,如上面的 ./admin.pem 不能省略 ./,否則返回 401 Unauthorized。

4.4 創建bear token 認證和授權

  1 [root@k8smaster01 ~]# kubectl create sa kubelet-api-test
  2 [root@k8smaster01 ~]# kubectl create clusterrolebinding kubelet-api-test --clusterrole=system:kubelet-api-admin --serviceaccount=default:kubelet-api-test
  3 [root@k8smaster01 ~]# SECRET=$(kubectl get secrets | grep kubelet-api-test | awk '{print $1}')
  4 [root@k8smaster01 ~]# TOKEN=$(kubectl describe secret ${SECRET} | grep -E '^token' | awk '{print $2}')
  5 [root@k8smaster01 ~]# echo ${TOKEN}




  1 [root@k8smaster01 ~]# curl -s --cacert /etc/kubernetes/cert/ca.pem -H "Authorization: Bearer ${TOKEN}" https://172.24.8.71:10250/metrics|head



4.5 cadvisor 和 metrics


cadvisor 是內嵌在 kubelet 二進制中的,統計所在節點各容器的資源(CPU、內存、磁盤、網卡)使用情況的服務。

瀏覽器訪問 https://172.24.8.71:10250/metrics 和 https://172.24.8.71:10250/metrics/cadvisor 分別返回 kubelet 和 cadvisor 的 metrics。

注意:

kubelet.config.json 設置 authentication.anonymous.enabled 為 false,不允許匿名證書訪問 10250 的 https 服務;

參考https://github.com/opsnull/follow-me-install-kubernetes-cluster/blob/master/A.%E6%B5%8F%E8%A7%88%E5%99%A8%E8%AE%BF%E9%97%AEkube-apiserver%E5%AE%89%E5%85%A8%E7%AB%AF%E5%8F%A3.md,創建和導入相關證書,然後訪問上面的 10250 端口。本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

SpringBoot 源碼解析 (三)—– Spring Boot 精髓:啟動時初始化數據

在我們用 springboot 搭建項目的時候,有時候會碰到在項目啟動時初始化一些操作的需求 ,針對這種需求 spring boot為我們提供了以下幾種方案供我們選擇:

ApplicationRunner與CommandLineRunner

我們可以實現 ApplicationRunner 或 CommandLineRunner 接口, 這兩個接口工作方式相同,都只提供單一的run方法,該方法在SpringApplication.run(…)完成之前調用,不知道大家還對我上一篇文章結尾有沒有印象,我們先來看看這兩個接口

public interface ApplicationRunner {
    void run(ApplicationArguments var1) throws Exception;
}

public interface CommandLineRunner {
    void run(String... var1) throws Exception;
}

都只提供單一的run方法,接下來我們來看看具體的使用

ApplicationRunner

構造一個類實現ApplicationRunner接口

//需要加入到Spring容器中
@Component public class ApplicationRunnerTest implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner");
    }
}

很簡單,首先要使用@Component將實現類加入到Spring容器中,為什麼要這樣做我們待會再看,然後實現其run方法實現自己的初始化數據邏輯就可以了

CommandLineRunner

對於這兩個接口而言,我們可以通過Order註解或者使用Ordered接口來指定調用順序, @Order() 中的值越小,優先級越高

//需要加入到Spring容器中
@Component
@Order(1)
public class CommandLineRunnerTest implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner...");
    }
}

同樣需要加入到Spring容器中,CommandLineRunner的參數是最原始的參數,沒有進行任何處理,ApplicationRunner的參數是ApplicationArguments,是對原始參數的進一步封裝

源碼分析

大家回顧一下我上一篇文章,也就是的最後一步,這裏我直接把代碼複製過來

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<Object>();
    //獲取容器中所有的ApplicationRunner的Bean實例
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); //獲取容器中所有的CommandLineRunner的Bean實例
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<Object>(runners)) {
        if (runner instanceof ApplicationRunner) {
            //執行ApplicationRunner的run方法
 callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            //執行CommandLineRunner的run方法
 callRunner((CommandLineRunner) runner, args);
        }
    }
}

很明顯,是直接從Spring容器中獲取ApplicationRunner和CommandLineRunner的實例,並調用其run方法,這也就是為什麼我要使用@Component將ApplicationRunner和CommandLineRunner接口的實現類加入到Spring容器中了。

InitializingBean

在spring初始化bean的時候,如果bean實現了 InitializingBean 接口,在對象的所有屬性被初始化后之後才會調用afterPropertiesSet()方法

@Component public class InitialingzingBeanTest implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean..");
    }
}

我們可以看出spring初始化bean肯定會在 ApplicationRunner和CommandLineRunner接口調用之前。

@PostConstruct

@Component public class PostConstructTest {

    @PostConstruct public void postConstruct() {
        System.out.println("init...");
    }
}

我們可以看到,只用在方法上添加@PostConstruct註解,並將類注入到Spring容器中就可以了。我們來看看@PostConstruct註解的方法是何時執行的

在Spring初始化bean時,對bean的實例賦值時,populateBean方法下面有一個initializeBean(beanName, exposedObject, mbd)方法,這個就是用來執行用戶設定的初始化操作。我們看下方法體:

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    if (System.getSecurityManager() != null) {
        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            // 激活 Aware 方法
            invokeAwareMethods(beanName, bean);
            return null;
        }, getAccessControlContext());
    }
    else {
        // 對特殊的 bean 處理:Aware、BeanClassLoaderAware、BeanFactoryAware
        invokeAwareMethods(beanName, bean);
    }

    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // 后處理器
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
        // 激活用戶自定義的 init 方法
 invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(
                (mbd != null ? mbd.getResourceDescription() : null),
                beanName, "Invocation of init method failed", ex);
    }
    if (mbd == null || !mbd.isSynthetic()) {
        // 后處理器
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    return wrappedBean;
}

我們看到會先執行后處理器然後執行invokeInitMethods方法,我們來看下applyBeanPostProcessorsBeforeInitialization

public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)  
        throws BeansException {  

    Object result = existingBean;  
    for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { result = beanProcessor.postProcessBeforeInitialization(result, beanName);  
        if (result == null) {  
            return result;  
        }  
    }  
    return result;  
}

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)  
        throws BeansException {  

    Object result = existingBean;  
    for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {  
        result = beanProcessor.postProcessAfterInitialization(result, beanName);  
        if (result == null) {  
            return result;  
        }  
    }  
    return result;  
}

獲取容器中所有的後置處理器,循環調用後置處理器的postProcessBeforeInitialization方法,這裏我們來看一個BeanPostProcessor

public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
    public CommonAnnotationBeanPostProcessor() {
        this.setOrder(2147483644);
        //設置初始化參數為PostConstruct.class
        this.setInitAnnotationType(PostConstruct.class); this.setDestroyAnnotationType(PreDestroy.class);
        this.ignoreResourceType("javax.xml.ws.WebServiceContext");
    }
    //略...
}

在構造器中設置了一個屬性為PostConstruct.class,再次觀察CommonAnnotationBeanPostProcessor這個類,它繼承自InitDestroyAnnotationBeanPostProcessor。InitDestroyAnnotationBeanPostProcessor顧名思義,就是在Bean初始化和銷毀的時候所作的一個前置/後置處理器。查看InitDestroyAnnotationBeanPostProcessor類下的postProcessBeforeInitialization方法:

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  
  LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());  
   try {  
      metadata.invokeInitMethods(bean, beanName);  
   }  
   catch (InvocationTargetException ex) {  
       throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());  
   }  
   catch (Throwable ex) {  
       throw new BeanCreationException(beanName, "Couldn't invoke init method", ex);  
   }  
    return bean;  
}  

private LifecycleMetadata buildLifecycleMetadata(final Class clazz) {  
       final LifecycleMetadata newMetadata = new LifecycleMetadata();  
       final boolean debug = logger.isDebugEnabled();  
       ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {  
           public void doWith(Method method) {  
              if (initAnnotationType != null) {  
                   //判斷clazz中的methon是否有initAnnotationType註解,也就是PostConstruct.class註解
                  if (method.getAnnotation(initAnnotationType) != null) { //如果有就將方法添加進LifecycleMetadata中
 newMetadata.addInitMethod(method);  
                     if (debug) {  
                         logger.debug("Found init method on class [" + clazz.getName() + "]: " + method);  
                     }  
                  }  
              }  
              if (destroyAnnotationType != null) {  
                    //判斷clazz中的methon是否有destroyAnnotationType註解
                  if (method.getAnnotation(destroyAnnotationType) != null) {  
                     newMetadata.addDestroyMethod(method);  
                     if (debug) {  
                         logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);  
                     }  
                  }  
              }  
           }  
       });  
       return newMetadata;  
} 

在這裡會去判斷某方法是否有PostConstruct.class註解,如果有,則添加到init/destroy隊列中,後續一一執行。@PostConstruct註解的方法會在此時執行,我們接着來看invokeInitMethods

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
        throws Throwable {

    // 是否實現 InitializingBean // 如果實現了 InitializingBean 接口,則只掉調用bean的 afterPropertiesSet()
    boolean isInitializingBean = (bean instanceof InitializingBean); if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        if (logger.isDebugEnabled()) {
            logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
        }
        if (System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                    ((InitializingBean) bean).afterPropertiesSet();
                    return null;
                }, getAccessControlContext());
            }
            catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        }
        else {
            // 直接調用 afterPropertiesSet()
 ((InitializingBean) bean).afterPropertiesSet();
        }
    }

    if (mbd != null && bean.getClass() != NullBean.class) {
        // 判斷是否指定了 init-method(),
        // 如果指定了 init-method(),則再調用制定的init-method
        String initMethodName = mbd.getInitMethodName();
        if (StringUtils.hasLength(initMethodName) &&
                !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                !mbd.isExternallyManagedInitMethod(initMethodName)) {
            // 利用反射機制執行
            invokeCustomInitMethod(beanName, bean, mbd);
        }
    }
}

首先檢測當前 bean 是否實現了 InitializingBean 接口,如果實現了則調用其 afterPropertiesSet(),然後再檢查是否也指定了 init-method(),如果指定了則通過反射機制調用指定的 init-method()

我們也可以發現@PostConstruct會在實現 InitializingBean 接口的afterPropertiesSet()方法之前執行

Spring的事件機制

基礎概念

Spring的事件驅動模型由三部分組成

  • 事件: ApplicationEvent ,繼承自JDK的 EventObject ,所有事件都要繼承它,也就是被觀察者
  • 事件發布者: ApplicationEventPublisher 及 ApplicationEventMulticaster 接口,使用這個接口,就可以發布事件了
  • 事件監聽者: ApplicationListener ,繼承JDK的 EventListener ,所有監聽者都繼承它,也就是我們所說的觀察者,當然我們也可以使用註解 @EventListener ,效果是一樣的

事件

在Spring框架中,默認對ApplicationEvent事件提供了如下支持:

  • ContextStartedEvent:ApplicationContext啟動后觸發的事件
  • ContextStoppedEvent:ApplicationContext停止后觸發的事件
  • ContextRefreshedEvent: ApplicationContext初始化或刷新完成后觸發的事件 ;(容器初始化完成后調用,所以我們可以利用這個事件做一些初始化操作)
  • ContextClosedEvent:ApplicationContext關閉后觸發的事件;(如 web 容器關閉時自動會觸發spring容器的關閉,如果是普通 java 應用,需要調用ctx.registerShutdownHook();註冊虛擬機關閉時的鈎子才行) 

構造一個類繼承ApplicationEvent

public class TestEvent extends ApplicationEvent {

    private String message;
    
    public TestEvent(Object source) {
        super(source);
    }

    public void getMessage() {
        System.out.println(message);
    }

    public void setMessage(String message) {
        this.message = message;
    }

}

創建事件監聽者

有兩種方法可以創建監聽者,一種是直接實現ApplicationListener的接口,一種是使用註解 @EventListener , 註解是添加在監聽方法上的 ,下面的例子是直接實現的接口

@Component
public class ApplicationListenerTest implements ApplicationListener<TestEvent> {
    @Override
    public void onApplicationEvent(TestEvent testEvent) {
        testEvent.getMessage();
    }
}

事件發布

對於事件發布,代表者是 ApplicationEventPublisher 和 ApplicationEventMulticaster ,ApplicationContext接口繼承了ApplicationEventPublisher,並在AbstractApplicationContext實現了具體代碼,實際執行是委託給ApplicationEventMulticaster(可以認為是多播)

下面是一個事件發布者的測試實例:

@RunWith(SpringRunner.class)
@SpringBootTest
public class EventTest {
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void publishTest() {
        TestEvent testEvent = new TestEvent("");
        testEvent.setMessage("hello world");
       applicationContext.publishEvent(testEvent);
    }
}

利用ContextRefreshedEvent事件進行初始化操作

利用 ContextRefreshedEvent 事件進行初始化,該事件是 ApplicationContext 初始化完成后調用的事件,所以我們可以利用這個事件,對應實現一個 監聽器 ,在其 onApplicationEvent() 方法里初始化操作

@Component public class ApplicationListenerTest implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("容器刷新完成后,我被調用了..");
    }
}

 

 

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

上海擬擴增4000組電動車充電樁

為深化EVCARD專案合作,中國國網上海是電力公司與上海國際汽車城集團簽屬戰略協議,將在上海新增4,000組電動車充電設備,促進上海市的電動車租賃業發展。

《新民晚報》報導,國往上海電力公司與上海國際汽車城將在上海三處建置新的充電設備,位置在漕溪電動車示範充電站(華石路38號)、浦東新區政府三角地、上海火車站北廣場下沉廣場。3日啟用後,用戶可透過手機EVCARD APP連線使用。未來,充電網將逐步提供EVCARD租賃車輛熱點租還車服務,將提高電動車租賃的便利性。

國網上海市電力公司表示,今年該公司規劃在上海市建設229座城市快充站、22做城際高速快充站、14做公共充電站,共將新增4,000做充電樁。2016年間,目標覆蓋轄區內所有高速公路充電服務。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

奇瑞新建輕量電動車生產線 年產能可達6萬輛

據報導,奇瑞新工廠將投產鋁車身骨架純電動乘用車,年產能可達6萬輛,主要生產S51EV、小螞蟻、AOEV等系列電動車。此外新工廠投產後,將成為奇瑞兌現年產20萬輛新能源車產能目標的重要一環。

奇瑞新電動車生產項目位於蕪湖弋江區高新技術開發區,項目總投資15.6億元人民幣,由奇瑞汽車股份有限公司獨資建設,有望於年內正式投產。

奇瑞汽車股份有限公司總經理助理劉志佳此前表示,奇瑞後續的新能源汽車產品,都是以鋁合金框架(含電池鋁合金框架)和分體式碳纖維於一體的輕量化產品構架平臺。今年10月,奇瑞將推出該輕量化平臺打造的首款車型,車身減重將達到30%,該車型的推出會為減排起到很大作用,同時新車的製造過程對於傳統四大工藝(衝壓、焊裝、塗裝、總裝)也將是很大的挑戰。

據劉志佳介紹,2016年奇瑞將新能源車銷量目標定為3.3萬輛,其中純電車目標為28,000輛,插電式混合動力車目標是5,000輛。與2015年銷量表現相比,其今年銷量將實現133%的增幅。

按照奇瑞此前公佈的消息,奇瑞目前已形成全系列的新能源乘用車研發體系和集成平臺,包括四大新能源整車平臺(小型純電動平臺、中型純電動平臺、插電式混動平臺、電四驅平臺);五大通用子系統(動力電池系統、電驅動系統、整車控制系統、PHEV動力總成、電驅動後橋);七大核心技術(電池管理技術、電池系統設計、整車控制技術、PHEV系統設計、智慧互聯設計、輕量化技術、新能源整車集成)。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

cocos creator 3D | 螞蟻莊園運動會星星球

上一篇文章寫了一個簡易版的螞蟻莊園登山賽,有小夥伴留言說想要看星星球的,那麼就寫起來吧!

效果預覽

配置環境 cocos creator 3d 1.0.0

小球點擊

3d里節點無法用 cc.Node.EventType.TOUCH_START 監聽。最終在論壇上找到一個 raycast 解決方法。參考代碼如下。

start() {
    systemEvent.on(SystemEventType.TOUCH_START, this.onClickBall, this);
}
private _ray = new geometry.ray();
private onClickBall(touch: Touch, event: EventTouch) {
    const pos = touch.getLocation();
    this.camera.screenPointToRay(pos.x, pos.y, this._ray);
    const result: { node: Node }[] = this.node_ball_click.scene.renderScene['raycast'](this._ray);
    if (result.some((i) => {
        if (i.node === this.node_ball_click) {
            return true;
        }
    })) {
        //點擊到小球處理邏輯
    }
}

其中 result 返回的是一個包含node節點的結果數組。獲取后需要判斷一下是否為小球節點。

據說這個方案消耗性能比較大,後續應該會有更好的解決方案。

動畫系統

採用了編輯器的動畫編輯器,對需要部分增加動畫效果。由於我的資源是網上找的,那隻雞有些身體部分切割的不好,所以小雞的動畫比較差一些。

需要注意的是動畫編輯器里的rotation屬性,與節點里的屬性面板的rotation對應不上,而應該採用eulerAngles的屬性。

據說後續版本會處理?

小球軌跡

採用tween控制小球坐標數值,先移動到最高點,然後再移動到最低點。

在運動軌跡中加入一些隨機值,就可以達到不同位置的效果啦。

tweenUtil(this._node_balll_pos)
    .stop()
    .to(time, new math.Vec3((this.node_ball.position.x + BALL_INIT_X) / 2, BALL_MAX_Y * (0.8 + 0.2 * Math.random()), targetZ / 2))
    .to(time, new math.Vec3(BALL_GAMEOVER_X, BALL_MIN_Y, targetZ))
    .start();

小結

完成這個小功能主要遇到的問題是3d節點點擊事件,和動畫系統的rotation的問題。不過這些都在論壇里找到了相應的解決方法。

以上就是我最新的學習成果!如有問題或新的想法歡迎留言!我有了好想法會第一時間分享給大家的!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

Docker學習(五)-Kubernetes 集群搭建 – Spring Boot 應用,Docker學習-Docker搭建Consul集群,Docker學習-簡單的私有DockerHub搭建,Docker學習-Spring Boot on Docker,Docker學習-Kubernetes – 集群部署,Docker學習-Kubernetes – Spring Boot 應用,Docker學習-Kubernetes – Spring Boot 應用

 

Docker學習

 

 

簡介

kubernetes,簡稱K8s,是用8代替8個字符“ubernete”而成的縮寫。是一個開源的,用於管理雲平台中多個主機上的容器化的應用,Kubernetes的目標是讓部署容器化的應用簡單並且高效(powerful),Kubernetes提供了應用部署,規劃,更新,維護的一種機制。

Kubernetes是Google開源的一個容器編排引擎,它支持自動化部署、大規模可伸縮、應用容器化管理。在生產環境中部署一個應用程序時,通常要部署該應用的多個實例以便對應用請求進行負載均衡。 在Kubernetes中,我們可以創建多個容器,每個容器裏面運行一個應用實例,然後通過內置的負載均衡策略,實現對這一組應用實例的管理、發現、訪問,而這些細節都不需要運維人員去進行複雜的手工配置和處理。  

基本概念

Kubernetes 中的絕大部分概念都抽象成 Kubernetes 管理的一種資源對象

  • Master:Master 節點是 Kubernetes 集群的控制節點,負責整個集群的管理和控制。Master 節點上包含以下組件:
  • kube-apiserver:集群控制的入口,提供 HTTP REST 服務
  • kube-controller-manager:Kubernetes 集群中所有資源對象的自動化控制中心
  • kube-scheduler:負責 Pod 的調度
  • Node:Node 節點是 Kubernetes 集群中的工作節點,Node 上的工作負載由 Master 節點分配,工作負載主要是運行容器應用。Node 節點上包含以下組件:

    • kubelet:負責 Pod 的創建、啟動、監控、重啟、銷毀等工作,同時與 Master 節點協作,實現集群管理的基本功能。
    • kube-proxy:實現 Kubernetes Service 的通信和負載均衡
    • 運行容器化(Pod)應用
  • Pod: Pod 是 Kubernetes 最基本的部署調度單元。每個 Pod 可以由一個或多個業務容器和一個根容器(Pause 容器)組成。一個 Pod 表示某個應用的一個實例

  • ReplicaSet:是 Pod 副本的抽象,用於解決 Pod 的擴容和伸縮
  • Deployment:Deployment 表示部署,在內部使用ReplicaSet 來實現。可以通過 Deployment 來生成相應的 ReplicaSet 完成 Pod 副本的創建
  • Service:Service 是 Kubernetes 最重要的資源對象。Kubernetes 中的 Service 對象可以對應微服務架構中的微服務。Service 定義了服務的訪問入口,服務的調用者通過這個地址訪問 Service 後端的 Pod 副本實例。Service 通過 Label Selector 同後端的 Pod 副本建立關係,Deployment 保證後端Pod 副本的數量,也就是保證服務的伸縮性。

Kubernetes 主要由以下幾個核心組件組成:

  • etcd 保存了整個集群的狀態,就是一個數據庫;
  • apiserver 提供了資源操作的唯一入口,並提供認證、授權、訪問控制、API 註冊和發現等機制;
  • controller manager 負責維護集群的狀態,比如故障檢測、自動擴展、滾動更新等;
  • scheduler 負責資源的調度,按照預定的調度策略將 Pod 調度到相應的機器上;
  • kubelet 負責維護容器的生命周期,同時也負責 Volume(CSI)和網絡(CNI)的管理;
  • Container runtime 負責鏡像管理以及 Pod 和容器的真正運行(CRI);
  • kube-proxy 負責為 Service 提供 cluster 內部的服務發現和負載均衡;

當然了除了上面的這些核心組件,還有一些推薦的插件:

  • kube-dns 負責為整個集群提供 DNS 服務
  • Ingress Controller 為服務提供外網入口
  • Heapster 提供資源監控
  • Dashboard 提供 GUI

組件通信

Kubernetes 多組件之間的通信原理:

  • apiserver 負責 etcd 存儲的所有操作,且只有 apiserver 才直接操作 etcd 集群
  • apiserver 對內(集群中的其他組件)和對外(用戶)提供統一的 REST API,其他組件均通過 apiserver 進行通信

    • controller manager、scheduler、kube-proxy 和 kubelet 等均通過 apiserver watch API 監測資源變化情況,並對資源作相應的操作
    • 所有需要更新資源狀態的操作均通過 apiserver 的 REST API 進行
  • apiserver 也會直接調用 kubelet API(如 logs, exec, attach 等),默認不校驗 kubelet 證書,但可以通過 --kubelet-certificate-authority 開啟(而 GKE 通過 SSH 隧道保護它們之間的通信)

比如最典型的創建 Pod 的流程:​​

  • 用戶通過 REST API 創建一個 Pod
  • apiserver 將其寫入 etcd
  • scheduluer 檢測到未綁定 Node 的 Pod,開始調度並更新 Pod 的 Node 綁定
  • kubelet 檢測到有新的 Pod 調度過來,通過 container runtime 運行該 Pod
  • kubelet 通過 container runtime 取到 Pod 狀態,並更新到 apiserver 中

 

集群部署

 

使用kubeadm工具安裝

1. master和node 都用yum 安裝kubelet,kubeadm,docker
2. master 上初始化:kubeadm init
3. master 上啟動一個flannel的pod
4. node上加入集群:kubeadm join

 

準備環境

Centos7  192.168.50.21 k8s-master  
Centos7  192.168.50.22 k8s-node01
Centos7  192.168.50.23 k8s-node02

修改主機名(3台機器都需要修改)

hostnamectl set-hostname k8s-master
hostnamectl set-hostname k8s-node01
hostnamectl set-hostname k8s-node02

關閉防火牆

systemctl stop firewalld.service

配置docker yum源

yum install -y yum-utils device-mapper-persistent-data lvm2 wget
cd /etc/yum.repos.d
wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

配置kubernetes yum 源

cd /opt/
wget https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
wget https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
rpm --import yum-key.gpg
rpm --import rpm-package-key.gpg
cd /etc/yum.repos.d
vi kubernetes.repo 
輸入以下內容
[kubernetes]
name=Kubernetes Repo
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
enabled=1

yum repolist

master和node 安裝kubelet,kubeadm,docker

yum install docker 
yum install kubelet-1.13.1
yum install kubeadm-1.13.1

master 上安裝kubectl

yum install kubectl-1.13.1

docker的配置

配置私有倉庫和鏡像加速地址,私有倉庫配置參見 

vi /etc/docker/daemon.json

 

{
    "registry-mirror":[
        "http://hub-mirror.c.163.com"
    ],
    "insecure-registries":[
        "192.168.50.24:5000"
    ]
}

 

啟動docker

systemctl daemon-reload
systemctl start docker 
docker info

master 上初始化:kubeadm init 

vi /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS="--fail-swap-on=false"
kubeadm init \
    --apiserver-advertise-address=192.168.50.21 \
    --image-repository registry.aliyuncs.com/google_containers \
    --kubernetes-version v1.13.1 \
    --pod-network-cidr=10.244.0.0/16

初始化命令說明:

--apiserver-advertise-address

指明用 Master 的哪個 interface 與 Cluster 的其他節點通信。如果 Master 有多個 interface,建議明確指定,如果不指定,kubeadm 會自動選擇有默認網關的 interface。

--pod-network-cidr

指定 Pod 網絡的範圍。Kubernetes 支持多種網絡方案,而且不同網絡方案對 –pod-network-cidr 有自己的要求,這裏設置為 10.244.0.0/16 是因為我們將使用 flannel 網絡方案,必須設置成這個 CIDR。

--image-repository

Kubenetes默認Registries地址是 k8s.gcr.io,在國內並不能訪問 gcr.io,在1.13版本中我們可以增加–image-repository參數,默認值是 k8s.gcr.io,將其指定為阿里雲鏡像地址:registry.aliyuncs.com/google_containers。

--kubernetes-version=v1.13.1 

關閉版本探測,因為它的默認值是stable-1,會導致從https://dl.k8s.io/release/stable-1.txt下載最新的版本號,我們可以將其指定為固定版本(最新版:v1.13.1)來跳過網絡請求。

初始化過程中

[preflight] You can also perform this action in beforehand using 'kubeadm config images pull' 是在下載鏡像文件,過程比較慢。
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 24.002300 seconds 這個過程也比較慢 可以忽略
 
[init] Using Kubernetes version: v1.13.1
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8s-master kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.50.21]
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8s-master localhost] and IPs [192.168.50.21 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8s-master localhost] and IPs [192.168.50.21 127.0.0.1 ::1]
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 24.002300 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.13" in namespace kube-system with the configuration for the kubelets in the cluster
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "k8s-master" as an annotation
[mark-control-plane] Marking the node k8s-master as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node k8s-master as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: 7ax0k4.nxpjjifrqnbrpojv
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join 192.168.50.21:6443 --token 7ax0k4.nxpjjifrqnbrpojv --discovery-token-ca-cert-hash sha256:95942f10859a71879c316e75498de02a8b627725c37dee33f74cd040e1cd9d6b

初始化過程說明:

1) [preflight] kubeadm 執行初始化前的檢查。
2) [kubelet-start] 生成kubelet的配置文件”/var/lib/kubelet/config.yaml”
3) [certificates] 生成相關的各種token和證書
4) [kubeconfig] 生成 KubeConfig 文件,kubelet 需要這個文件與 Master 通信
5) [control-plane] 安裝 Master 組件,會從指定的 Registry 下載組件的 Docker 鏡像。
6) [bootstraptoken] 生成token記錄下來,後邊使用kubeadm join往集群中添加節點時會用到
7) [addons] 安裝附加組件 kube-proxy 和 kube-dns。
8) Kubernetes Master 初始化成功,提示如何配置常規用戶使用kubectl訪問集群。
9) 提示如何安裝 Pod 網絡。
10) 提示如何註冊其他節點到 Cluster。

 

異常情況:

 [WARNING Service-Docker]: docker service is not enabled, please run 'systemctl enable docker.service'
        [WARNING Swap]: running with swap on is not supported. Please disable swap
        [WARNING Hostname]: hostname "k8s-master" could not be reached
        [WARNING Hostname]: hostname "k8s-master": lookup k8s-master on 114.114.114.114:53: no such host
        [WARNING Service-Kubelet]: kubelet service is not enabled, please run 'systemctl enable kubelet.service'

運行

systemctl enable docker.service
systemctl enable kubelet.service

會提示以下錯誤

  [WARNING Hostname]: hostname "k8s-master" could not be reached
        [WARNING Hostname]: hostname "k8s-master": lookup k8s-master on 114.114.114.114:53: no such host
error execution phase preflight: [preflight] Some fatal errors occurred:

配置host

cat >> /etc/hosts << EOF
192.168.50.21 k8s-master
192.168.50.22 k8s-node01
192.168.50.23 k8s-node02
EOF

再次運行初始化命令會出現

  [ERROR NumCPU]: the number of available CPUs 1 is less than the required 2   --設置虛擬機CPU個數大於2
        [ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables contents are not set to 1
echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables

設置好虛擬機CPU個數,重啟后再次運行:

kubeadm init \
    --apiserver-advertise-address=192.168.50.21 \
    --image-repository registry.aliyuncs.com/google_containers \
    --kubernetes-version v1.13.1 \
    --pod-network-cidr=10.244.0.0/16

 

[init] Using Kubernetes version: v1.13.1
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull

解決辦法:docker.io倉庫對google的容器做了鏡像,可以通過下列命令下拉取相關鏡像

先看下需要用到哪些

kubeadm config images list

配置yum源

[root@k8s-master opt]# vi kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: v1.13.1
imageRepository: registry.aliyuncs.com/google_containers
apiServer:
  certSANs:
  - 192.168.50.21
controlPlaneEndpoint: "192.168.50.20:16443"
networking:
  # This CIDR is a Calico default. Substitute or remove for your CNI provider.
  podSubnet: "172.168.0.0/16"
 kubeadm config images pull --config /opt/kubeadm-config.yaml

初始化master

kubeadm init --config=kubeadm-config.yaml  --upload-certs

 

xecution phase preflight: [preflight] Some fatal errors occurred:
        [ERROR FileAvailable--etc-kubernetes-manifests-kube-apiserver.yaml]: /etc/kubernetes/manifests/kube-apiserver.yaml already exists
        [ERROR FileAvailable--etc-kubernetes-manifests-kube-controller-manager.yaml]: /etc/kubernetes/manifests/kube-controller-manager.yaml already exists
        [ERROR FileAvailable--etc-kubernetes-manifests-kube-scheduler.yaml]: /etc/kubernetes/manifests/kube-scheduler.yaml already exists
        [ERROR FileAvailable--etc-kubernetes-manifests-etcd.yaml]: /etc/kubernetes/manifests/etcd.yaml already exists
        [ERROR Port-10250]: Port 10250 is in use

kubeadm會自動檢查當前環境是否有上次命令執行的“殘留”。如果有,必須清理后再行執行init。我們可以通過”kubeadm reset”來清理環境,以備重來。

[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[kubelet-check] Initial timeout of 40s passed.

==原因==

這是因為kubelet沒啟動

==解決==

systemctl restart kubelet

如果啟動不了kubelet

kubelet.service - kubelet: The Kubernetes Node Agent

 

則可能是swap交換分區還開啟的原因
-關閉swap

swapoff -a

-配置kubelet

vi /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS="--fail-swap-on=false"

 再次運行

kubeadm init \
    --apiserver-advertise-address=192.168.50.21 \
    --image-repository registry.aliyuncs.com/google_containers \
    --kubernetes-version v1.13.1 \
    --pod-network-cidr=10.244.0.0/16

 

 

配置 kubectl

kubectl 是管理 Kubernetes Cluster 的命令行工具,前面我們已經在所有的節點安裝了 kubectl。Master 初始化完成后需要做一些配置工作,然後 kubectl 就能使用了。
依照 kubeadm init 輸出的最後提示,推薦用 Linux 普通用戶執行 kubectl。

  • 創建普通用戶centos
#創建普通用戶並設置密碼123456
useradd centos && echo "centos:123456" | chpasswd centos

#追加sudo權限,並配置sudo免密
sed -i '/^root/a\centos  ALL=(ALL)       NOPASSWD:ALL' /etc/sudoers

#保存集群安全配置文件到當前用戶.kube目錄
su - centos
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

#啟用 kubectl 命令自動補全功能(註銷重新登錄生效)
echo "source <(kubectl completion bash)" >> ~/.bashrc

需要這些配置命令的原因是:Kubernetes 集群默認需要加密方式訪問。所以,這幾條命令,就是將剛剛部署生成的 Kubernetes 集群的安全配置文件,保存到當前用戶的.kube 目錄下,kubectl 默認會使用這個目錄下的授權信息訪問 Kubernetes 集群。
如果不這麼做的話,我們每次都需要通過 export KUBECONFIG 環境變量告訴 kubectl 這個安全配置文件的位置。
配置完成后centos用戶就可以使用 kubectl 命令管理集群了。

查看集群狀態:

kubectl get cs

 

 

 

 

 

 部署網絡插件

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

  

kubectl get 重新檢查 Pod 的狀態

 

 

部署worker節點

 在master機器保存生成號的鏡像文件

docker save -o master.tar registry.aliyuncs.com/google_containers/kube-proxy:v1.13.1 registry.aliyuncs.com/google_containers/kube-apiserver:v1.13.1 registry.aliyuncs.com/google_containers/kube-controller-manager:v1.13.1 registry.aliyuncs.com/google_containers/kube-scheduler:v1.13.1  registry.aliyuncs.com/google_containers/coredns:1.2.6  registry.aliyuncs.com/google_containers/etcd:3.2.24 registry.aliyuncs.com/google_containers/pause:3.1

注意對應的版本號

將master上保存的鏡像同步到節點上

scp master.tar node01:/root/
scp master.tar node02:/root/

將鏡像導入本地,node01,node02

 docker load< master.tar

配置host,node01,node02

cat >> /etc/hosts << EOF
192.168.50.21 k8s-master
192.168.50.22 k8s-node01
192.168.50.23 k8s-node02
EOF

配置iptables,node01,node02

echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables

-關閉swap,node01,node02

swapoff -a

-配置kubelet,node01,node02

vi /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS="--fail-swap-on=false" 
systemctl enable docker.service
systemctl enable kubelet.service

啟動docker,node01,node02

service docker strat

部署網絡插件,node01,node02

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

獲取join指令,master

kubeadm token create --print-join-command
kubeadm token create --print-join-command
kubeadm join 192.168.50.21:6443 --token n9g4nq.kf8ppgpgb3biz0n5 --discovery-token-ca-cert-hash sha256:95942f10859a71879c316e75498de02a8b627725c37dee33f74cd040e1cd9d6b

 

在子節點運行指令 ,node01,node02

kubeadm join 192.168.50.21:6443 --token n9g4nq.kf8ppgpgb3biz0n5 --discovery-token-ca-cert-hash sha256:95942f10859a71879c316e75498de02a8b627725c37dee33f74cd040e1cd9d6b
[preflight] Running pre-flight checks
[discovery] Trying to connect to API Server "192.168.50.21:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.50.21:6443"
[discovery] Requesting info from "https://192.168.50.21:6443" again to validate TLS against the pinned public key
[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "192.168.50.21:6443"
[discovery] Successfully established connection with API Server "192.168.50.21:6443"
[join] Reading configuration from the cluster...
[join] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] WARNING: unable to stop the kubelet service momentarily: [exit status 4]
[kubelet] Downloading configuration for the kubelet from the "kubelet-config-1.13" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Activating the kubelet service
[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "k8s-node01" as an annotation

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the master to see this node join the cluster.

在master上查看節點狀態

kubectl get nodes

 

 這種狀態是錯誤的 ,只有一台聯機正確

查看node01,和node02發現 node01有些進程沒有完全啟動

刪除node01所有運行的容器,node01

docker stop $(docker ps -q) & docker rm $(docker ps -aq)

重置 kubeadm ,node01

kubeadm reset

獲取join指令,master

kubeadm token create --print-join-command

再次在node01上運行join

 

 

 

查看node01鏡像運行狀態

  

查看master狀態

  

nodes狀態全部為ready,由於每個節點都需要啟動若干組件,如果node節點的狀態是 NotReady,可以查看所有節點pod狀態,確保所有pod成功拉取到鏡像並處於running狀態:

kubectl get pod --all-namespaces -o wide

  

配置kubernetes UI圖形化界面

創建kubernetes-dashboard.yaml

# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# ------------------- Dashboard Secret ------------------- #

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kube-system
type: Opaque

---
# ------------------- Dashboard Service Account ------------------- #

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system

---
# ------------------- Dashboard Role & Role Binding ------------------- #

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: kubernetes-dashboard-minimal
  namespace: kube-system
rules:
  # Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret.
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["create"]
  # Allow Dashboard to create 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["create"]
  # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs"]
  verbs: ["get", "update", "delete"]
  # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["kubernetes-dashboard-settings"]
  verbs: ["get", "update"]
  # Allow Dashboard to get metrics from heapster.
- apiGroups: [""]
  resources: ["services"]
  resourceNames: ["heapster"]
  verbs: ["proxy"]
- apiGroups: [""]
  resources: ["services/proxy"]
  resourceNames: ["heapster", "http:heapster:", "https:heapster:"]
  verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kubernetes-dashboard-minimal
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kubernetes-dashboard-minimal
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kube-system

---
# ------------------- Dashboard Deployment ------------------- #

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
    spec:
      containers:
      - name: kubernetes-dashboard
        image: registry.cn-hangzhou.aliyuncs.com/rsqlh/kubernetes-dashboard:v1.10.1  
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8443
          protocol: TCP
        args:
          - --auto-generate-certificates
          # Uncomment the following line to manually specify Kubernetes API server Host
          # If not specified, Dashboard will attempt to auto discover the API server and connect
          # to it. Uncomment only if the default does not work.
          # - --apiserver-host=http://my-address:port
        volumeMounts:
        - name: kubernetes-dashboard-certs
          mountPath: /certs
          # Create on-disk volume to store exec logs
        - mountPath: /tmp
          name: tmp-volume
        livenessProbe:
          httpGet:
            scheme: HTTPS
            path: /
            port: 8443
          initialDelaySeconds: 30
          timeoutSeconds: 30
      volumes:
      - name: kubernetes-dashboard-certs
        secret:
          secretName: kubernetes-dashboard-certs
      - name: tmp-volume
        emptyDir: {}
      serviceAccountName: kubernetes-dashboard
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule

---
# ------------------- Dashboard Service ------------------- #

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system
spec:
  type: NodePort
  ports:
  - port: 443
    targetPort: 8443
    nodePort: 30000
  selector:
    k8s-app: kubernetes-dashboard

 

執行以下命令創建kubernetes-dashboard:

kubectl create -f kubernetes-dashboard.yaml

如果出現

Error from server (AlreadyExists): error when creating "kubernetes-dashboard.yaml": secrets "kubernetes-dashboard-certs" already exists
Error from server (AlreadyExists): error when creating "kubernetes-dashboard.yaml": serviceaccounts "kubernetes-dashboard" already exists
Error from server (AlreadyExists): error when creating "kubernetes-dashboard.yaml": roles.rbac.authorization.k8s.io "kubernetes-dashboard-minimal" already exists
Error from server (AlreadyExists): error when creating "kubernetes-dashboard.yaml": rolebindings.rbac.authorization.k8s.io "kubernetes-dashboard-minimal" already exists
Error from server (AlreadyExists): error when creating "kubernetes-dashboard.yaml": deployments.apps "kubernetes-dashboard" already exists

運行delete清理

kubectl delete -f kubernetes-dashboard.yaml

查看組件運行狀態

kubectl get pods --all-namespaces

 

 

 ErrImagePull 拉取鏡像失敗

手動拉取 並重置tag

docker pull registry.cn-hangzhou.aliyuncs.com/rsqlh/kubernetes-dashboard:v1.10.1
docker tag registry.cn-hangzhou.aliyuncs.com/rsqlh/kubernetes-dashboard:v1.10.1 k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1

重新創建

  

 

ImagePullBackOff

默認情況是會根據配置文件中的鏡像地址去拉取鏡像,如果設置為IfNotPresent 和Never就會使用本地鏡像。

IfNotPresent :如果本地存在鏡像就優先使用本地鏡像。
Never:直接不再去拉取鏡像了,使用本地的;如果本地不存在就報異常了。

 spec:
      containers:
      - name: kubernetes-dashboard
        image: registry.cn-hangzhou.aliyuncs.com/rsqlh/kubernetes-dashboard:v1.10.1  
        imagePullPolicy: IfNotPresent

 

查看映射狀態 

 kubectl get service  -n kube-system

 

 

 

 

創建能夠訪問 Dashboard 的用戶

 新建文件 account.yaml ,內容如下:

# Create Service Account
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system
---
# Create ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')

 

 複製token登陸

 

 

configmaps is forbidden: User "system:serviceaccount:kube-system:admin-user" cannot list resource "configmaps" in API group "" in the namespace "default" 

 授權用戶

kubectl create clusterrolebinding test:admin-user --clusterrole=cluster-admin --serviceaccount=kube-system:admin-user

 

 

 NodePort方式,可以到任意一個節點的XXXX端口查看

 

 

本文參考:

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

git回退之git reset

參考

https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E9%87%8D%E7%BD%AE%E6%8F%AD%E5%AF%86

https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified

https://git-scm.com/docs/git-reset

https://www.liaoxuefeng.com/wiki/896043488029600/897013573512192

從歷史記錄中刪除 參考 https://www.cnblogs.com/studywithallofyou/p/11772684.html https://www.cnblogs.com/studywithallofyou/p/11772844.html

前言

在使用git的時候,我們一般提倡是不允許回滾。對於問題的追蹤和項目的發展歷程而言歷史記錄都是有用的。並且為了節省一點存儲空間而丟失寶貴的代碼信息是不值當的。但是我們開發中,肯定會遇到特殊情況需要回退。比如確實操作錯了一步歷史提交,導致倉庫混亂污染或是內容丟失,我們需要回退到乾淨的一次提交,重新操作。

在git等所有的版本管理軟件中,刪除操作,只是增加一次記錄,內容並不會被刪掉,我們可以大膽的操作,這也符合版本倉庫的邏輯。只有特殊情況才需要真正的刪除,做這種操作的時候需要特別注意,因為一旦失誤,無法挽回。

工作流程

要想理解git reset,那麼就要搞清楚git倉庫管理流程:

我們修改完內容后,這些記錄單單是保存在我們本地目錄下,也就是工作目錄。如果丟了,就是丟了,無法找回。與普通磁盤上的文件一樣,除非到回收站找回。

這時,如果運行了add,那麼內容就被記錄到本地的暫存倉庫,也就是index。這時如果刪除文件,在暫存區的內容還存在,並沒有丟失。

如下圖,我們創建了一個文件4,這時文件4在本地。我們add到暫存倉庫,然後刪掉文件4,我們發現又多了一條記錄刪除記錄,原來的4還存在,我們可以commit。原來的文件4並沒有丟失。

運行commit之後,修改的內容就被保存到了倉庫,修改了HEAD(HEAD就是指向當前倉庫在哪一個提交歷史,不特殊修改,都指向最新的一次提交)。這時運行git status發現目錄是乾淨的。git裏面需要提交倉庫,暫存倉庫和本地目錄內容都完全一致,才是乾淨的,任何一個不一致都會有不同的提示。

比如上面的圖,提交的倉庫中沒有文件4,暫存倉庫中增加了4,所以显示綠色的提示內容,add了文件但是還沒有commit。但是本次又刪除了文件,本地目錄中的內容與暫存倉庫也不一樣,所以提示紅色的內容,因為算是警告,沒有add的內容是會丟失的。

修改

add

commit

git reset的三個選擇

理解了上面的流程,就可以理解git reset了。git reset就是分別逆向操作,也就是把HEAD(提交的倉庫)回退到一個指定歷史,把HEAD、index(暫存倉庫)都回退到指定歷史,把HEAD、index和本地目錄的內容都回退到一個指定歷史。

git reset –soft就是把HEAD回退到指定歷史。運行后結果如下

也就相當於我們add了修改的文件,本地目錄和暫存倉庫都已經一致了,就等待commit。如果我們commit,可以再次填寫commit記錄,也就實現了git commit –amend的功能。

git reset –mixed就是把HEAD和index都回退到指定的歷史。這個也是運行git reset不加參數時的默認規則。運行結果如下

相當於我們僅僅修改了文件,還沒做任何處理。

git reset –hard這個是把HEAD、index和本地目錄的內容都回退到指定的歷史記錄。做這一步操作的時候一定要小心,最好把所有的內容都提交,並且push到遠程或是拷貝一份。因為這個操作會重置本地的內容到一個指定歷史。

我們先運行git log,看到有三個提交歷史

運行git reset –hard HEAD~

我們發現倉庫是乾淨的,並且原來的3文件沒了,運行git log參看

提交的歷史記錄也沒有了。

這次是真的回退到了指定歷史,所有的記錄都不見了,我們可以開心的(真的嗎?)在原來一個乾淨的分支上繼續寫代碼了。

git reset –hard的後悔葯

世上有沒有後悔葯我不知道,但是git作為一個先進的分佈式管理器,卻有無限可能。如果你一不小心,腦袋發熱,運行了git reset –hard,但是發現不是你想要的,原來的記錄也沒了。怎麼辦?大腦瞬間充血,一片空白。不要慌,首先冷靜下來,然後運行git reflog,這個命令是告訴你你的每一次對倉庫操作的歷史記錄,如下

看一下最上面的幾條,第一條告訴你當前在e86d948這個提交記錄,通過reset切換過來的。對照上面的git log,可以發現這個是第二個提交歷史,也就是我們git reset –hard HEAD~的時候回退的分支。第二條記錄告訴我們當前是fc9fbc6分支,通過reset回退到這個分支的,我們可以參考上面的git log記錄,這個就是我們想要回去的分支。好了,有了記錄的哈希值,我們只需再運行一次git reset –hard,如下

查看一下git log

回來了。

git不會刪除任何已經提交到版本庫的內容,除非你非要專門特殊這樣做,並且為了再給你一次機會,像git reset這樣,就算你明確說明不要了,它也不會立馬刪除,除非超過一定時間或是你主動運行git gc等操作,把無用的,沒有關聯的內容刪掉。不然,那條記錄還是在本地倉庫,只不過它沒有被載入歷史的長河中。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

016.Kubernetes二進制部署所有節點kube-proxy

一 部署 kube-proxy


kube-proxy 運行在所有節點上,它監聽 apiserver 中 service 和 endpoint 的變化情況,創建路由規則以提供服務 IP 和負載均衡功能。

1.1 安裝kube-proxy


提示:k8smaster01節點已下載相應二進制,可直接分發至node節點。

1.2 分發kube-proxy

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     scp kubernetes/server/bin/kube-proxy root@${all_ip}:/opt/k8s/bin/
  7     ssh root@${all_ip} "chmod +x /opt/k8s/bin/*"
  8   done


1.3 創建kube-scheduler證書和私鑰

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > kube-proxy-csr.json <<EOF
  3 {
  4   "CN": "system:kube-proxy",
  5   "key": {
  6     "algo": "rsa",
  7     "size": 2048
  8   },
  9   "names": [
 10     {
 11       "C": "CN",
 12       "ST": "Shanghai",
 13       "L": "Shanghai",
 14       "O": "k8s",
 15       "OU": "System"
 16     }
 17   ]
 18 }
 19 EOF
 20 #創建kube-scheduler的CA證書請求文件



解釋:

  • CN:指定該證書的 User 為 system:kube-proxy;
  • 預定義的 RoleBinding system:node-proxier 將User system:kube-proxy 與 Role system:node-proxier 綁定,該 Role 授予了調用 kube-apiserver Proxy 相關 API 的權限;
  • 該證書只會被 kube-proxy 當做 client 證書使用,所以 hosts 字段為空。




  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cfssl gencert -ca=/opt/k8s/work/ca.pem \
  3 -ca-key=/opt/k8s/work/ca-key.pem -config=/opt/k8s/work/ca-config.json \
  4 -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy		#生成CA密鑰(ca-key.pem)和證書(ca.pem)


1.4 創建和分發kubeconfig


kube-proxy 使用 kubeconfig 文件訪問 apiserver,該文件提供了 apiserver 地址、嵌入的 CA 證書和 kube-proxy 證書:

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# kubectl config set-cluster kubernetes \
  4   --certificate-authority=/opt/k8s/work/ca.pem \
  5   --embed-certs=true \
  6   --server=${KUBE_APISERVER} \
  7   --kubeconfig=kube-proxy.kubeconfig
  8 
  9 [root@k8smaster01 work]# kubectl config set-credentials kube-proxy \
 10   --client-certificate=kube-proxy.pem \
 11   --client-key=kube-proxy-key.pem \
 12   --embed-certs=true \
 13   --kubeconfig=kube-proxy.kubeconfig
 14 
 15 [root@k8smaster01 work]# kubectl config set-context default \
 16   --cluster=kubernetes \
 17   --user=kube-proxy \
 18   --kubeconfig=kube-proxy.kubeconfig
 19 
 20 [root@k8smaster01 work]# kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig
 21 
 22 [root@k8smaster01 ~]# cd /opt/k8s/work
 23 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
 24 [root@k8smaster01 work]# for node_name in ${NODE_NAMES[@]}
 25   do
 26     echo ">>> ${node_name}"
 27     scp kube-proxy.kubeconfig root@${node_name}:/etc/kubernetes/
 28   done


1.5 創建kube-proxy 配置文件


從 v1.10 開始,kube-proxy 部分參數可以配置文件中配置。可以使用 –write-config-to 選項生成該配置文件。

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > kube-proxy-config.yaml.template <<EOF
  3 kind: KubeProxyConfiguration
  4 apiVersion: kubeproxy.config.k8s.io/v1alpha1
  5 clientConnection:
  6   burst: 200
  7   kubeconfig: "/etc/kubernetes/kube-proxy.kubeconfig"
  8   qps: 100
  9 bindAddress: ##ALL_IP##
 10 healthzBindAddress: ##ALL_IP##:10256
 11 metricsBindAddress: ##ALL_IP##:10249
 12 enableProfiling: true
 13 clusterCIDR: ${CLUSTER_CIDR}
 14 hostnameOverride: ##ALL_NAME##
 15 mode: "ipvs"
 16 portRange: ""
 17 kubeProxyIPTablesConfiguration:
 18   masqueradeAll: false
 19 kubeProxyIPVSConfiguration:
 20   scheduler: rr
 21   excludeCIDRs: []
 22 EOF



解釋:

  • bindAddress: 監聽地址;
  • clientConnection.kubeconfig: 連接 apiserver 的 kubeconfig 文件;
  • clusterCIDR: kube-proxy 根據 –cluster-cidr 判斷集群內部和外部流量,指定 –cluster-cidr 或 –masquerade-all 選項后 kube-proxy 才會對訪問 Service IP 的請求做 SNAT;
  • hostnameOverride: 參數值必須與 kubelet 的值一致,否則 kube-proxy 啟動後會找不到該 Node,從而不會創建任何 ipvs 規則;
  • mode: 使用 ipvs 模式。

1.6 分發配置文件

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for (( i=0; i < 6; i++ ))
  4   do
  5     echo ">>> ${ALL_NAMES[i]}"
  6     sed -e "s/##ALL_NAME##/${ALL_NAMES[i]}/" -e "s/##ALL_IP##/${ALL_IPS[i]}/" kube-proxy-config.yaml.template > kube-proxy-config-${ALL_NAMES[i]}.yaml.template
  7     scp kube-proxy-config-${ALL_NAMES[i]}.yaml.template root@${ALL_NAMES[i]}:/etc/kubernetes/kube-proxy-config.yaml
  8   done


1.7 創建kube-proxy的systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# cat > kube-proxy.service <<EOF
  4 [Unit]
  5 Description=Kubernetes Kube-Proxy Server
  6 Documentation=https://github.com/GoogleCloudPlatform/kubernetes
  7 After=network.target
  8 
  9 [Service]
 10 WorkingDirectory=${K8S_DIR}/kube-proxy
 11 ExecStart=/opt/k8s/bin/kube-proxy \\
 12   --config=/etc/kubernetes/kube-proxy-config.yaml \\
 13   --logtostderr=true \\
 14   --v=2
 15 Restart=on-failure
 16 RestartSec=5
 17 LimitNOFILE=65536
 18 
 19 [Install]
 20 WantedBy=multi-user.target
 21 EOF


1.8 分發kube-proxy systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_name in ${ALL_NAMES[@]}
  4   do
  5     echo ">>> ${all_name}"
  6     scp kube-proxy.service root@${all_name}:/etc/systemd/system/
  7   done						#分發system


二 啟動並驗證

2.1 啟動kube-proxy 服務

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     ssh root@${all_ip} "mkdir -p ${K8S_DIR}/kube-proxy"
  7     ssh root@${all_ip} "modprobe ip_vs_rr"
  8     ssh root@${all_ip} "systemctl daemon-reload && systemctl enable kube-proxy && systemctl restart kube-proxy"
  9   done						#啟動服務前必須先創建工作目錄


2.2 檢查kube-proxy 服務

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for all_ip in ${ALL_IPS[@]}
  3   do
  4     echo ">>> ${all_ip}"
  5     ssh root@${all_ip} "systemctl status kube-proxy|grep Active"
  6   done


2.3 查看監聽端口


kube-proxy 監聽 10249 和 10256 端口:

  • 10249:對外提供 /metrics;
  • 10256:對外提供 /healthz 的訪問。

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     ssh root@${all_ip} "sudo netstat -lnpt|grep kube-prox"
  7   done



2.4 查看ipvs 路由規則

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     ssh root@${all_ip} "/usr/sbin/ipvsadm -ln"
  7   done



可見所有通過 https 訪問 K8S SVC kubernetes 的請求都轉發到 kube-apiserver 節點的 6443 端口。本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

從EFCore上下文的使用到深入剖析DI的生命周期最後實現自動屬性注入

故事背景

最近在把自己的一個老項目從Framework遷移到.Net Core 3.0,數據訪問這塊選擇的是EFCore+Mysql。使用EF的話不可避免要和DbContext打交道,在Core中的常規用法一般是:創建一個XXXContext類繼承自DbContext,實現一個擁有DbContextOptions參數的構造器,在啟動類StartUp中的ConfigureServices方法里調用IServiceCollection的擴展方法AddDbContext,把上下文注入到DI容器中,然後在使用的地方通過構造函數的參數獲取實例。OK,沒任何毛病,官方示例也都是這麼來用的。但是,通過構造函數這種方式來獲取上下文實例其實很不方便,比如在Attribute或者靜態類中,又或者是系統啟動時初始化一些數據,更多的是如下一種場景:

    public class BaseController : Controller
    {
        public BloggingContext _dbContext;
        public BaseController(BloggingContext dbContext)
        {
            _dbContext = dbContext;
        }

        public bool BlogExist(int id)
        {
            return _dbContext.Blogs.Any(x => x.BlogId == id);
        }
    }

    public class BlogsController : BaseController
    {
        public BlogsController(BloggingContext dbContext) : base(dbContext) { }
    }

從上面的代碼可以看到,任何要繼承BaseController的類都要寫一個“多餘”的構造函數,如果參數再多幾個,這將是無法忍受的(就算只有一個參數我也忍受不了)。那麼怎樣才能更優雅的獲取數據庫上下文實例呢,我想到以下幾種辦法。


DbContext從哪來

1、  直接開new

回歸原始,既然要創建實例,沒有比直接new一個更好的辦法了,在Framework中沒有DI的時候也差不多都這麼干。但在EFCore中不同的是,DbContext不再提供無參構造函數,取而代之的是必須傳入一個DbContextOptions類型的參數,這個參數通常是做一些上下文選項配置例如使用什麼類型數據庫連接字符串是多少。

        public BloggingContext(DbContextOptions<BloggingContext> options) : base(options)
        {
        }

默認情況下,我們已經在StartUp中註冊上下文的時候做了配置,DI容器會自動幫我們把options傳進來。如果要手動new一個上下文,那豈不是每次都要自己傳?不行,這太痛苦了。那有沒有辦法不傳這個參數?肯定也是有的。我們可以去掉有參構造函數,然後重寫DbContext中的OnConfiguring方法,在這個方法中做數據庫配置: 

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("Filename=./efcoredemo.db");
        }

即使是這樣,依然有不夠優雅的地方,那就是連接字符串被硬編碼在代碼中,不能做到從配置文件讀取。反正我忍受不了,只能再尋找其他方案。

2、  從DI容器手動獲取

既然前面已經在啟動類中註冊了上下文,那麼從DI容器中獲取實例肯定是沒問題的。於是我寫了這樣一句測試代碼用來驗證猜想:

    var context = app.ApplicationServices.GetService<BloggingContext>();

不過很遺憾拋出了異常:

報錯信息說的很明確,不能從root provider中獲取這個服務。我從G站下載了DI框架的源碼(地址是),拿報錯信息進行反向追溯,發現異常來自於CallSiteValidator類的ValidateResolution方法:

        public void ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
        {
            if (ReferenceEquals(scope, rootScope)
                && _scopedServices.TryGetValue(serviceType, out var scopedService))
            {
                if (serviceType == scopedService)
                {
                    throw new InvalidOperationException(
                        Resources.FormatDirectScopedResolvedFromRootException(serviceType,
                            nameof(ServiceLifetime.Scoped).ToLowerInvariant()));
                }

                throw new InvalidOperationException(
                    Resources.FormatScopedResolvedFromRootException(
                        serviceType,
                        scopedService,
                        nameof(ServiceLifetime.Scoped).ToLowerInvariant()));
            }
        }

View Code

繼續往上,看到了GetService方法的實現:

        internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
        {
            if (_disposed)
            {
                ThrowHelper.ThrowObjectDisposedException();
            }

            var realizedService = RealizedServices.GetOrAdd(serviceType, _createServiceAccessor);
            _callback?.OnResolve(serviceType, serviceProviderEngineScope);
            DependencyInjectionEventSource.Log.ServiceResolved(serviceType);
            return realizedService.Invoke(serviceProviderEngineScope);
        }

View Code

可以看到,_callback在為空的情況下是不會做驗證的,於是猜想有參數能對它進行配置。把追溯對象換成_callback繼續往上翻,在DI框架的核心類ServiceProvider中找到如下方法:

        internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
        {
            IServiceProviderEngineCallback callback = null;
            if (options.ValidateScopes)
            {
                callback = this;
                _callSiteValidator = new CallSiteValidator();
            }
            //省略....
        }    

說明我的猜想沒錯,驗證是受ValidateScopes控制的。這樣來看,把ValidateScopes設置成False就可以解決了,這也是網上普遍的解決方案:

      .UseDefaultServiceProvider(options =>
       {
              options.ValidateScopes = false;
       })

但這樣做是極其危險的。

為什麼危險?到底什麼是root provider?那就要從原生DI的生命周期說起。我們知道,DI容器被封裝成一個IServiceProvider對象,服務都是從這裏來獲取。不過這並不是一個單一對象,它是具有層級結構的,最頂層的即前面提到的root provider,可以理解為僅屬於系統層面的DI控制中心。在Asp.Net Core中,內置的DI有3種服務模式,分別是SingletonTransientScoped,Singleton服務實例是保存在root provider中的,所以它才能做到全局單例。相對應的Scoped,是保存在某一個provider中的,它能保證在這個provider中是單例的,而Transient服務則是隨時需要隨時創建,用完就丟棄。由此可知,除非是在root provider中獲取一個單例服務,否則必須要指定一個服務範圍(Scope),這個驗證是通過ServiceProviderOptionsValidateScopes來控制的。默認情況下,Asp.Net Core框架在創建HostBuilder的時候會判定當前是否開發環境,在開發環境下會開啟這個驗證:

所以前面那種關閉驗證的方式是錯誤的。這是因為,root provider只有一個,如果恰好有某個singleton服務引用了一個scope服務,這會導致這個scope服務也變成singleton,仔細看一下註冊DbContext的擴展方法,它實際上提供的是scope服務:

如果發生這種情況,數據庫連接會一直得不到釋放,至於有什麼後果大家應該都明白。

所以前面的測試代碼應該這樣寫:

     using (var serviceScope = app.ApplicationServices.CreateScope())
     {
         var context = serviceScope.ServiceProvider.GetService<BloggingContext>();
     }

與之相關的還有一個ValidateOnBuild屬性,也就是說在構建IServiceProvider的時候就會做驗證,從源碼中也能體現出來:

            if (options.ValidateOnBuild)
            {
                List<Exception> exceptions = null;
                foreach (var serviceDescriptor in serviceDescriptors)
                {
                    try
                    {
                        _engine.ValidateService(serviceDescriptor);
                    }
                    catch (Exception e)
                    {
                        exceptions = exceptions ?? new List<Exception>();
                        exceptions.Add(e);
                    }
                }

                if (exceptions != null)
                {
                    throw new AggregateException("Some services are not able to be constructed", exceptions.ToArray());
                }
            }

View Code

正因為如此,Asp.Net Core在設計的時候為每個請求創建獨立的Scope,這個Scope的provider被封裝在HttpContext.RequestServices中。

 [小插曲]

通過代碼提示可以看到,IServiceProvider提供了2種獲取service的方式:

這2個有什麼區別呢?分別查看各自的方法摘要可以看到,通過GetService獲取一個沒有註冊的服務時會返回null,而GetRequiredService會拋出一個InvalidOperationException,僅此而已。

        // 返回結果:
        //     A service object of type T or null if there is no such service.
        public static T GetService<T>(this IServiceProvider provider);

        // 返回結果:
        //     A service object of type T.
        //
        // 異常:
        //   T:System.InvalidOperationException:
        //     There is no service of type T.
        public static T GetRequiredService<T>(this IServiceProvider provider);

 

終極大招

到現在為止,儘管找到了一種看起來合理的方案,但還是不夠優雅,使用過其他第三方DI框架的朋友應該知道,屬性注入的快感無可比擬。那原生DI有沒有實現這個功能呢,我滿心歡喜上G站搜Issue,看到這樣一個回復():

官方明確表示沒有開發屬性注入的計劃,沒辦法,只能靠自己了。

我的思路大概是:創建一個自定義標籤(Attribute),用來給需要注入的屬性打標籤,然後寫一個服務激活類,用來解析給定實例需要注入的屬性並賦值,在某個類型被創建實例的時候也就是構造函數中調用這個激活方法實現屬性注入。這裡有個核心點要注意的是,從DI容器獲取實例的時候一定要保證是和當前請求是同一個Scope,也就是說,必須要從當前的HttpContext中拿到這個IServiceProvider

先創建一個自定義標籤:

    [AttributeUsage(AttributeTargets.Property)]
    public class AutowiredAttribute : Attribute
    {

    }

解析屬性的方法:

        public void PropertyActivate(object service, IServiceProvider provider)
        {
            var serviceType = service.GetType();
            var properties = serviceType.GetProperties().AsEnumerable().Where(x => x.Name.StartsWith("_"));
            foreach (PropertyInfo property in properties)
            {
                var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>();
                if (autowiredAttr != null)
                {
                    //從DI容器獲取實例
                    var innerService = provider.GetService(property.PropertyType);
                    if (innerService != null)
                    {
                        //遞歸解決服務嵌套問題
                        PropertyActivate(innerService, provider);
                        //屬性賦值
                        property.SetValue(service, innerService);
                    }
                }
            }
        }

然後在控制器中激活屬性:

        [Autowired]
        public IAccountService _accountService { get; set; }

        public LoginController(IHttpContextAccessor httpContextAccessor)
        {
            var pro = new AutowiredServiceProvider();
            pro.PropertyActivate(this, httpContextAccessor.HttpContext.RequestServices);
        }

這樣子下來,雖然功能實現了,但是裏面存着幾個問題。第一個是由於控制器的構造函數中不能直接使用ControllerBaseHttpContext屬性,所以必須要通過注入IHttpContextAccessor對象來獲取,貌似問題又回到原點。第二個是每個構造函數中都要寫這麼一堆代碼,不能忍。於是想有沒有辦法在控制器被激活的時候做一些操作?沒考慮引入AOP框架,感覺為了這一個功能引入AOP有點重。經過網上搜索,發現Asp.Net Core框架激活控制器是通過IControllerActivator接口實現的,它的默認實現是DefaultControllerActivator):

       /// <inheritdoc />
        public object Create(ControllerContext controllerContext)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException(nameof(controllerContext));
            }

            if (controllerContext.ActionDescriptor == null)
            {
                throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull(
                    nameof(ControllerContext.ActionDescriptor),
                    nameof(ControllerContext)));
            }

            var controllerTypeInfo = controllerContext.ActionDescriptor.ControllerTypeInfo;

            if (controllerTypeInfo == null)
            {
                throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull(
                    nameof(controllerContext.ActionDescriptor.ControllerTypeInfo),
                    nameof(ControllerContext.ActionDescriptor)));
            }

            var serviceProvider = controllerContext.HttpContext.RequestServices;
            return _typeActivatorCache.CreateInstance<object>(serviceProvider, controllerTypeInfo.AsType());
        }

View Code

這樣一來,我自己實現一個Controller激活器不就可以接管控制器激活了,於是有如下這個類:

    public class HosControllerActivator : IControllerActivator
    {
        public object Create(ControllerContext actionContext)
        {
            var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType();
            var instance = actionContext.HttpContext.RequestServices.GetRequiredService(controllerType);
            PropertyActivate(instance, actionContext.HttpContext.RequestServices);
            return instance;
        }

        public virtual void Release(ControllerContext context, object controller)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (controller == null)
            {
                throw new ArgumentNullException(nameof(controller));
            }
            if (controller is IDisposable disposable)
            {
                disposable.Dispose();
            }
        }

        private void PropertyActivate(object service, IServiceProvider provider)
        {
            var serviceType = service.GetType();
            var properties = serviceType.GetProperties().AsEnumerable().Where(x => x.Name.StartsWith("_"));
            foreach (PropertyInfo property in properties)
            {
                var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>();
                if (autowiredAttr != null)
                {
                    //從DI容器獲取實例
                    var innerService = provider.GetService(property.PropertyType);
                    if (innerService != null)
                    {
                        //遞歸解決服務嵌套問題
                        PropertyActivate(innerService, provider);
                        //屬性賦值
                        property.SetValue(service, innerService);
                    }
                }
            }
        }
    }

View Code

需要注意的是,DefaultControllerActivator中的控制器實例是從TypeActivatorCache獲取的,而自己的激活器是從DI獲取的,所以必須額外把系統所有控制器註冊到DI中,封裝成如下的擴展方法:

        /// <summary>
        /// 自定義控制器激活,並手動註冊所有控制器
        /// </summary>
        /// <param name="services"></param>
        /// <param name="obj"></param>
        public static void AddHosControllers(this IServiceCollection services, object obj)
        {
            services.Replace(ServiceDescriptor.Transient<IControllerActivator, HosControllerActivator>());
            var assembly = obj.GetType().GetTypeInfo().Assembly;
            var manager = new ApplicationPartManager();
            manager.ApplicationParts.Add(new AssemblyPart(assembly));
            manager.FeatureProviders.Add(new ControllerFeatureProvider());
            var feature = new ControllerFeature();
            manager.PopulateFeature(feature);
            feature.Controllers.Select(ti => ti.AsType()).ToList().ForEach(t =>
            {
                services.AddTransient(t);
            });
        }

View Code

ConfigureServices中調用:

services.AddHosControllers(this);

到此,大功告成!可以愉快的繼續CRUD了。

 

結尾

市面上好用的DI框架一堆一堆的,集成到Core裏面也很簡單,為啥還要這麼折騰?沒辦法,這不就是造輪子的樂趣嘛。上面這些東西從頭到尾也折騰了不少時間,屬性注入那裡也還有優化的空間,歡迎探討。

推薦閱讀:

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

初識web API接口及Restful接口規範

一、web API接口

什麼是web API接口?:

明確了請求方式,提供對應後台所需參數,請求url鏈接可以得到後台的響應數據
url : 返回數據的url

請求方式:
get,post,put,patch….

請求參數:json或xml格式的key-value類型數據

  • ak:6E823f587c95f0148c19993539b99295
  • region:上海
  • query:肯德基
  • output:json

響應結果:
​ 返回json或xml格式的key-value類型數據

  • 上方請求參數的output參數值決定了響應數據的格式

  • {
        "status":0,
        "message":"ok",
        "results":[
            {
                "name":"肯德基(羅餐廳)",
                "location":{
                    "lat":31.415354,
                    "lng":121.357339
                },
                "address":"月羅路2380號",
                "province":"上海市",
                "city":"上海市",
                "area":"寶山區",
                "street_id":"339ed41ae1d6dc320a5cb37c",
                "telephone":"(021)56761006",
                "detail":1,
                "uid":"339ed41ae1d6dc320a5cb37c"
            }
            ...
            ]
    }

怎麼寫接口?:

​ 參照某種規則(規範)書寫url鏈接,同時根據規則制定請求方式,請求數據與響應結果

接口文檔:

​ 提供給前後台開發人員與測試人員查看

接口工具:

YAPI平台

​ 專門寫接口文檔的YAPI平台

​ YApi是去哪兒網的前端技術中心的一個開源可視化接口管理平台

  • 創建接口項目
  • 創建接口
  • 編寫接口

接口測試工具:Postman

Postman是一款接口調試工具,是一款免費的可視化軟件,同時支持各種操作系統平台,是測試接口的首選工具。

Postman可以直接從下載獲得,然後進行傻瓜式安裝。

  • 工作面板
  • 簡易的get請求
  • 簡易的post請求
  • 案例:請求百度地圖接口

接口規範:

​ webapi接口規範:restful

RESTful API介紹

RESTful介紹

REST與技術無關,代表的是一種軟件架構風格,REST是Representational State Transfer的簡稱,中文翻譯為“表徵狀態轉移”或“表現層狀態轉化”。

推薦閱讀

RESTful API設計指南

推薦閱讀

API與用戶的通信協議

總是使用。

域名

用api關鍵字來標識接口url

注:看到api字眼,就代表該請求url鏈接是完成前後台數據交互的

版本

\1. 將版本信息放在URL中,如:

​ v1,v2代表不同數據版本的提現,前提是一種數據資源有多個版本

\2. 將版本信息放在請求頭中。

url路徑

視網絡上任何東西都是資源,均使用名詞表示(一般為複數形式)

在url鏈接中獎勵不要出現操作資源的動詞

​ 錯誤示範:

特殊的接口可以出現動詞,因為這些接口一般沒有一個明確的資源,或是動詞就是接口的核心含義

method請求方式

GET :從服務器取出資源(一項或多項)

POST :在服務器新建一個資源

PUT :在服務器更新資源(客戶端提供改變后的完整資源)

PATCH :在服務器更新資源(客戶端提供改變的屬性)

DELETE :從服務器刪除資源

過濾

通過在url上傳參的形式傳遞搜索條件

狀態碼

200 OK - [GET]:服務器成功返回用戶請求的數據,該操作是冪等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功。
202 Accepted - [*]:表示一個請求已經進入後台排隊(異步任務)
204 NO CONTENT - [DELETE]:用戶刪除數據成功。


301:永久重定向
302:暫時重定向


400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操作,該操作是冪等的。
401 Unauthorized - [*]:表示用戶沒有權限(令牌、用戶名、密碼錯誤)。
403 Forbidden - [*] 表示用戶得到授權(與401錯誤相對),但是訪問是被禁止的。
404 NOT FOUND - [*]:用戶發出的請求針對的是不存在的記錄,服務器沒有進行操作,該操作是冪等的。
406 Not Acceptable - [GET]:用戶請求的格式不可得(比如用戶請求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 當創建一個對象時,發生一個驗證錯誤。


500 INTERNAL SERVER ERROR - [*]:服務器發生錯誤,用戶將無法判斷髮出的請求是否成功。

錯誤處理

狀態碼是4xx時,應返回錯誤信息,error當做key。    

{
    error: "Invalid API key"
}

返回結果

針對不同操作,服務器向用戶返回的結果應該符合以下規範

GET     /collection:返回資源對象的列表(數組)
GET     /collection/resource:返回單個資源對象
POST    /collection:返回新生成的資源對象
PUT     /collection/resource:返回完整的資源對象
PATCH   /collection/resource:返回完整的資源對象
DELETE  /collection/resource:返回一個空文檔
{
    "status": 0,
    "msg": "ok",
    "results":[
        {
            "name":"肯德基(羅餐廳)",
            "location":{
                "lat":31.415354,
                "lng":121.357339
            },
            "address":"月羅路2380號",
            "province":"上海市",
            "city":"上海市",
            "area":"寶山區",
            "street_id":"339ed41ae1d6dc320a5cb37c",
            "telephone":"(021)56761006",
            "detail":1,
            "uid":"339ed41ae1d6dc320a5cb37c"
        }
        ...
        ]
}

Hypermedia API

RESTful API最好做到Hypermedia,即返回結果中提供鏈接,連向其他API方法,使得用戶不查文檔,也知道下一步應該做什麼。

{"link": {
  "rel":   "collection https://www.example.com/zoos",
  "href":  "https://api.example.com/zoos",
  "title": "List of zoos",
  "type":  "application/vnd.yourformat+json"
}}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務