本文由 發(fā)布,轉(zhuǎn)載請(qǐng)注明出處,如有問(wèn)題請(qǐng)聯(lián)系我們! 發(fā)布時(shí)間: 2021-08-26kubebuilder實(shí)戰(zhàn)之二:初次體驗(yàn)kubebuilder
加載中
熱烈歡迎瀏覽我的GitHub
https://github.com/zq2599/blog_demos
內(nèi)容:全部原創(chuàng)文章內(nèi)容篩選及配套設(shè)施源代碼,涉及到Java、Docker、Kubernetes、DevOPS等;
系列產(chǎn)品文章內(nèi)容連接
kubebuilder實(shí)戰(zhàn)演練之一:準(zhǔn)備工作
kubebuilder實(shí)戰(zhàn)演練之二:第一次感受kubebuilder
kubebuilder實(shí)戰(zhàn)演練之三:基本知識(shí)快評(píng)
kubebuilder實(shí)戰(zhàn)演練之四:operator要求表明和設(shè)計(jì)方案
kubebuilder實(shí)戰(zhàn)演練之五:operator編號(hào)
kubebuilder實(shí)戰(zhàn)演練之六:搭建布署運(yùn)作
kubebuilder實(shí)戰(zhàn)演練之七:webhook
kubebuilder實(shí)戰(zhàn)演練之八:知識(shí)要點(diǎn)隨記
這篇概述
文中是《kubebuilder實(shí)戰(zhàn)》系列產(chǎn)品的第二篇,前文將kubebuilder自然環(huán)境提前準(zhǔn)備結(jié)束,今日我們?cè)谶@里自然環(huán)境建立CRD和Controller,再部署到kubernetes自然環(huán)境而且認(rèn)證是不是起效,全篇文章由下面這些內(nèi)容構(gòu)成:
建立API(CRD和Controller)
搭建和布署CRD
編譯程序和運(yùn)作controller
建立CRD相匹配的案例
刪掉案例并終止controller
將controller制做成docker鏡像系統(tǒng)
卸載掉和清除
建立helloworld新項(xiàng)目
實(shí)行下列指令,建立helloworld新項(xiàng)目:
mkdir -p $GOPATH/src/helloworld cd $GOPATH/src/helloworld kubebuilder init --domain com.bolingcavalry
控制面板輸出相近以下幾點(diǎn):
[root@kubebuilder helloworld]# kubebuilder init --domain com.bolingcavalry Writing scaffold for you to edit... Get controller runtime: $ go get sigs.k8s.io/controller-runtime@v0.5.0 Update go.mod: $ go mod tidy Running make: $ make /root/gopath/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." go fmt ./... go vet ./... go build -o bin/manager main.go Next: define a resource with: $ kubebuilder create api
等候數(shù)分鐘后建立進(jìn)行,在$GOPATH/src/helloworld文件目錄下增加以下幾點(diǎn),由此可見(jiàn)這也是個(gè)規(guī)范的go module工程項(xiàng)目:
[root@kubebuilder ~]# tree $GOPATH/src/helloworld /root/gopath/src/helloworld ├── bin │ └── manager ├── config │ ├── certmanager │ │ ├── certificate.yaml │ │ ├── kustomization.yaml │ │ └── kustomizeconfig.yaml │ ├── default │ │ ├── kustomization.yaml │ │ ├── manager_auth_proxy_patch.yaml │ │ ├── manager_webhook_patch.yaml │ │ └── webhookcainjection_patch.yaml │ ├── manager │ │ ├── kustomization.yaml │ │ └── manager.yaml │ ├── prometheus │ │ ├── kustomization.yaml │ │ └── monitor.yaml │ ├── rbac │ │ ├── auth_proxy_client_clusterrole.yaml │ │ ├── auth_proxy_role_binding.yaml │ │ ├── auth_proxy_role.yaml │ │ ├── auth_proxy_service.yaml │ │ ├── kustomization.yaml │ │ ├── leader_election_role_binding.yaml │ │ ├── leader_election_role.yaml │ │ └── role_binding.yaml │ └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── service.yaml ├── Dockerfile ├── go.mod ├── go.sum ├── hack │ └── boilerplate.go.txt ├── main.go ├── Makefile └── PROJECT 9 directories, 30 files
建立API(CRD和Controller)
下面要建立資源有關(guān)的具體內(nèi)容了,group/version/kind這三一部分能夠 明確資源的唯一真實(shí)身份,指令以下:
cd $GOPATH/src/helloworld kubebuilder create api \ --group webapp \ --version v1 \ --kind Guestbook
控制面板會(huì)提示是不是建立資源(Create Resource [y/n]),鍵入y
下面控制面板會(huì)提示是不是建立控制板(Create Controller [y/n]),鍵入y
kubebuilder會(huì)依據(jù)上述指令增加好幾個(gè)文檔,如下圖白框所顯示:
搭建和布署CRD
kubebuilder給予的Makefile將搭建和部署安排大幅簡(jiǎn)單化,實(shí)行下列指令會(huì)將全新搭建的CRD布署在kubernetes上:
cd $GOPATH/src/helloworld make install
控制面板輸出以下內(nèi)容,提醒布署取得成功:
[root@kubebuilder helloworld]# make install /root/gopath/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases kustomize build config/crd | kubectl apply -f - Warning: apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16 , unavailable in v1.22 ; use apiextensions.k8s.io/v1 CustomResourceDefinition customresourcedefinition.apiextensions.k8s.io/guestbooks.webapp.com.bolingcavalry created
編譯程序和運(yùn)作controller
kubebuilder自動(dòng)生成的controller源代碼詳細(xì)地址是:$GOPATH/src/helloworld/controllers/guestbook_controller.go , 內(nèi)容以下:
package controllers import ( "context" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" webappv1 "helloworld/api/v1" ) // GuestbookReconciler reconciles a Guestbook object type GuestbookReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme } // kubebuilder:rbac:groups=webapp.com.bolingcavalry,resources=guestbooks,verbs=get;list;watch;create;update;patch;delete // kubebuilder:rbac:groups=webapp.com.bolingcavalry,resources=guestbooks/status,verbs=get;update;patch func (r *GuestbookReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("guestbook", req.NamespacedName) // your logic here return ctrl.Result{}, nil } func (r *GuestbookReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&webappv1.Guestbook{}). Complete(r) }
文中以感受基本上步驟為主導(dǎo),不深入分析源代碼,因此 對(duì)以上的編碼僅做少許改動(dòng),用以認(rèn)證是不是能起效,修改如下圖白框所顯示:
實(shí)行下列指令,會(huì)編譯程序并運(yùn)行剛剛改動(dòng)的controller:
cd $GOPATH/src/helloworld make run
這時(shí)控制面板輸出以下幾點(diǎn),這兒要留意,controller是在kubebuilder電腦運(yùn)作的,一旦應(yīng)用Ctrl c終斷控制面板,便會(huì)造成 controller終止:
[root@kubebuilder helloworld]# make run /root/gopath/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." go fmt ./... go vet ./... /root/gopath/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases go run ./main.go 2021-01-23T20:58:35.107 0800 INFO controller-runtime.metrics metrics server is starting to listen {"addr": ":8080"} 2021-01-23T20:58:35.108 0800 INFO setup starting manager 2021-01-23T20:58:35.108 0800 INFO controller-runtime.manager starting metrics server {"path": "/metrics"} 2021-01-23T20:58:35.108 0800 INFO controller-runtime.controller Starting EventSource {"controller": "guestbook", "source": "kind source: /, Kind="} 2021-01-23T20:58:35.208 0800 INFO controller-runtime.controller Starting Controller {"controller": "guestbook"} 2021-01-23T20:58:35.209 0800 INFO controller-runtime.controller Starting workers {"controller": "guestbook", "worker count": 1}
建立Guestbook資源的案例
如今kubernetes早已實(shí)施了Guestbook種類的CRD,并且相應(yīng)的controller也已已經(jīng)運(yùn)作中,能夠 試著建立Guestbook種類的案例了(等同于擁有pod的界定后,才能夠建立pod);
kubebuilder早已全自動(dòng)建立了一個(gè)種類的布署文檔:$GOPATH/src/helloworld/config/samples/webapp_v1_guestbook.yaml ,內(nèi)容以下,非常簡(jiǎn)單,下面我們就用這一文檔來(lái)建立Guestbook案例:
apiVersion: webapp.com.bolingcavalry/v1 kind: Guestbook metadata: name: guestbook-sample spec: # Add fields here foo: bar
再次開(kāi)啟一個(gè)控制面板,登陸kubebuilder電腦上,實(shí)行下列指令就可以建立Guestbook種類的案例:
cd $GOPATH/src/helloworld kubectl apply -f config/samples/
以下所顯示,控制面板提醒資源建立取得成功:
[root@kubebuilder helloworld]# kubectl apply -f config/samples/ guestbook.webapp.com.bolingcavalry/guestbook-sample created
用kubectl get指令能夠 見(jiàn)到案例早已建立:
[root@kubebuilder helloworld]# kubectl get Guestbook NAME AGE guestbook-sample 112s
用指令kubectl edit Guestbook guestbook-sample編寫(xiě)該案例,改動(dòng)的內(nèi)容如下圖白框所顯示:
7. 這時(shí)去controller所屬控制面板,能夠 見(jiàn)到增加和更改的操作方法都是有日志輸出,我們?cè)黾拥娜罩径际窃诶镞?,編碼啟用棧一目了然:
2021-01-24T09:51:50.418 0800 INFO controllers.Guestbook 1. default/guestbook-sample 2021-01-24T09:51:50.418 0800 INFO controllers.Guestbook 2. goroutine 188 [running]: runtime/debug.Stack(0xc0002a1808, 0xc0002fc600, 0x1b) /root/go/src/runtime/debug/stack.go:24 0x9f helloworld/controllers.(*GuestbookReconciler).Reconcile(0xc0003c9dd0, 0xc0002d02f9, 0x7, 0xc0002d02e0, 0x10, 0x12f449647b, 0xc000456f30, 0xc000456ea8, 0xc000456ea0) /root/gopath/src/helloworld/controllers/guestbook_controller.go:49 0x1a9 sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).reconcileHandler(0xc00022a480, 0x1430e00, 0xc0003e7560, 0x0) /root/gopath/pkg/mod/sigs.k8s.io/controller-runtime@v0.5.0/pkg/internal/controller/controller.go:256 0x166 sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).processNextWorkItem(0xc00022a480, 0xc000469600) /root/gopath/pkg/mod/sigs.k8s.io/controller-runtime@v0.5.0/pkg/internal/controller/controller.go:232 0xb0 sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).worker(0xc00022a480) /root/gopath/pkg/mod/sigs.k8s.io/controller-runtime@v0.5.0/pkg/internal/controller/controller.go:211 0x2b k8s.io/apimachinery/pkg/util/wait.JitterUntil.func1(0xc000292980) /root/gopath/pkg/mod/k8s.io/apimachinery@v0.17.2/pkg/util/wait/wait.go:152 0x5f k8s.io/apimachinery/pkg/util/wait.JitterUntil(0xc000292980, 0x3b9aca00, 0x0, 0x1609101, 0xc000102480) /root/gopath/pkg/mod/k8s.io/apimachinery@v0.17.2/pkg/util/wait/wait.go:153 0x105 k8s.io/apimachinery/pkg/util/wait.Until(0xc000292980, 0x3b9aca00, 0xc000102480) /root/gopath/pkg/mod/k8s.io/apimachinery@v0.17.2/pkg/util/wait/wait.go:88 0x4d created by sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Start.func1 /root/gopath/pkg/mod/sigs.k8s.io/controller-runtime@v0.5.0/pkg/internal/controller/controller.go:193 0x32d 2021-01-24T09:51:50.418 0800 DEBUG controller-runtime.controller Successfully Reconciled {"controller": "guestbook", "request": "default/guestbook-sample"} 2021-01-24T09:52:33.632 0800 INFO controllers.Guestbook 1. default/guestbook-sample 2021-01-24T09:52:33.633 0800 INFO controllers.Guestbook 2. goroutine 188 [running]: runtime/debug.Stack(0xc0002a1808, 0xc0003fa5e0, 0x1b) /root/go/src/runtime/debug/stack.go:24 0x9f helloworld/controllers.(*GuestbookReconciler).Reconcile(0xc0003c9dd0, 0xc0002d02f9, 0x7, 0xc0002d02e0, 0x10, 0x1d0410fe42, 0xc000456f30, 0xc000456ea8, 0xc000456ea0) /root/gopath/src/helloworld/controllers/guestbook_controller.go:49 0x1a9 sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).reconcileHandler(0xc00022a480, 0x1430e00, 0xc0003d24c0, 0x0) /root/gopath/pkg/mod/sigs.k8s.io/controller-runtime@v0.5.0/pkg/internal/controller/controller.go:256 0x166 sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).processNextWorkItem(0xc00022a480, 0xc000469600) /root/gopath/pkg/mod/sigs.k8s.io/controller-runtime@v0.5.0/pkg/internal/controller/controller.go:232 0xb0 sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).worker(0xc00022a480) /root/gopath/pkg/mod/sigs.k8s.io/controller-runtime@v0.5.0/pkg/internal/controller/controller.go:211 0x2b k8s.io/apimachinery/pkg/util/wait.JitterUntil.func1(0xc000292980) /root/gopath/pkg/mod/k8s.io/apimachinery@v0.17.2/pkg/util/wait/wait.go:152 0x5f k8s.io/apimachinery/pkg/util/wait.JitterUntil(0xc000292980, 0x3b9aca00, 0x0, 0x1609101, 0xc000102480) /root/gopath/pkg/mod/k8s.io/apimachinery@v0.17.2/pkg/util/wait/wait.go:153 0x105 k8s.io/apimachinery/pkg/util/wait.Until(0xc000292980, 0x3b9aca00, 0xc000102480) /root/gopath/pkg/mod/k8s.io/apimachinery@v0.17.2/pkg/util/wait/wait.go:88 0x4d created by sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Start.func1 /root/gopath/pkg/mod/sigs.k8s.io/controller-runtime@v0.5.0/pkg/internal/controller/controller.go:193 0x32d 2021-01-24T09:52:33.633 0800 DEBUG controller-runtime.controller Successfully Reconciled {"controller": "guestbook", "request": "default/guestbook-sample"}
刪掉案例并終止controller
不會(huì)再必須Guestbook案例的情況下,實(shí)行下列指令就可以刪掉:
cd $GOPATH/src/helloworld kubectl delete -f config/samples/
不會(huì)再必須controller的情況下,去它的控制面板應(yīng)用Ctrl c終斷就可以;
將controller制做成docker鏡像系統(tǒng)
到此,我們?cè)缫洋w會(huì)過(guò)去了kubebuilder的基本要素,但是具體工作環(huán)境中controller一般都是會(huì)運(yùn)作在kubernetes自然環(huán)境內(nèi),像上邊這類運(yùn)作在kubernetes以外的形式就有問(wèn)題了,我們來(lái)試一下將其制成docker鏡像系統(tǒng)隨后在kubernetes自然環(huán)境運(yùn)作;
這兒有一個(gè)規(guī)定,便是您要有一個(gè)kubernetes能夠 瀏覽的鏡像系統(tǒng)庫(kù)房,比如局域網(wǎng)絡(luò)內(nèi)的Harbor,或是公共性的hub.docker.com,我這為了更好地實(shí)際操作便捷挑選了hub.docker.com,應(yīng)用它的先決條件是有著hub.docker.com的注冊(cè)新賬號(hào);
在kubebuilder電腦,開(kāi)啟一個(gè)控制面板,實(shí)行docker login指令登陸,依據(jù)提醒鍵入hub.docker.com的賬號(hào)和登陸密碼,那樣就可以在當(dāng)今操縱臺(tái)子上實(shí)行docker push指令將鏡像系統(tǒng)消息推送到hub.docker.com上(這一平臺(tái)的互聯(lián)網(wǎng)很差,很有可能要登陸好幾回才能夠取得成功);
實(shí)行下列指令搭建docker鏡像系統(tǒng)并推薦到hub.docker.com,鏡像系統(tǒng)名叫bolingcavalry/guestbook:002:
cd $GOPATH/src/helloworld make docker-build docker-push IMG=bolingcavalry/guestbook:002
hub.docker.com的互聯(lián)網(wǎng)情況并不是一般的差,kubebuilder電腦的docker一定要設(shè)定鏡像系統(tǒng)加快,以上指令假如遭受請(qǐng)求超時(shí)不成功,請(qǐng)?jiān)僭噹谆兀酥?,搭建全過(guò)程中還會(huì)繼續(xù)免費(fèi)下載眾多go控制模塊的依靠,也必須您耐心等待,也比較容易碰到網(wǎng)絡(luò)問(wèn)題,必須數(shù)次再試,因此 ,最好應(yīng)用局域網(wǎng)絡(luò)內(nèi)構(gòu)建的Habor服務(wù)項(xiàng)目;
最后,指令運(yùn)行取得成功后輸出以下:
[root@kubebuilder helloworld]# make docker-build docker-push IMG=bolingcavalry/guestbook:002 /root/gopath/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." go fmt ./... go vet ./... /root/gopath/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases go test ./... -coverprofile cover.out ? helloworld [no test files] ? helloworld/api/v1 [no test files] ok helloworld/controllers 8.604s coverage: 0.0% of statements docker build . -t bolingcavalry/guestbook:002 Sending build context to Docker daemon 40.27MB Step 1/14 : FROM golang:1.13 as builder ---> d6f3656320fe Step 2/14 : WORKDIR /workspace ---> Using cache ---> 83d05ead1041 Step 3/14 : COPY go.mod go.mod ---> Using cache ---> ae3e15a529f4 Step 4/14 : COPY go.sum go.sum ---> Using cache ---> 082223532ccc Step 5/14 : RUN go mod download ---> Using cache ---> bcdcfa1d65ca Step 6/14 : COPY main.go main.go ---> Using cache ---> 81d6a629ca98 Step 7/14 : COPY api/ api/ ---> Using cache ---> 75f99b174e97 Step 8/14 : COPY controllers/ controllers/ ---> b130d9f47903 Step 9/14 : RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go ---> Running in 768880aca19f Removing intermediate container 768880aca19f ---> bb4a494d3b43 Step 10/14 : FROM gcr.io/distroless/static:nonroot ---> 947e6f3ed7c1 Step 11/14 : WORKDIR / ---> Using cache ---> 22cc43cef8fb Step 12/14 : COPY --from=builder /workspace/manager . ---> 2137778f22c0 Step 13/14 : USER nonroot:nonroot ---> Running in 18295673073d Removing intermediate container 18295673073d ---> f7545379ab1f Step 14/14 : ENTRYPOINT ["/manager"] ---> Running in 550c47dd61dc Removing intermediate container 550c47dd61dc ---> 31cb31a6b03f Successfully built 31cb31a6b03f Successfully tagged bolingcavalry/guestbook:002 docker push bolingcavalry/guestbook:002 The push refers to repository [docker.io/bolingcavalry/guestbook] 99035107a955: Pushed 728501c5607d: Layer already exists 002: digest: sha256:54f8ec88511cce5b04c5d65cc15e0f7a7b4a8afb6b235904a638bff79e3c5784 size: 739
去hub.docker.com網(wǎng)站看一下,如下圖,新鏡像系統(tǒng)早已提交,那樣只需一切設(shè)備只需能上網(wǎng)就能pull此鏡像系統(tǒng)到本地運(yùn)用了:
8. 鏡像系統(tǒng)準(zhǔn)備好以后,實(shí)行下列指令就可以在kubernetes自然環(huán)境布署controller:
cd $GOPATH/src/helloworld make deploy IMG=bolingcavalry/guestbook:002
控制面板會(huì)提醒各種資源被建立(rbac占多數(shù)):
[root@kubebuilder ~]# cd $GOPATH/src/helloworld [root@kubebuilder helloworld]# make deploy IMG=bolingcavalry/guestbook:002 /root/gopath/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases cd config/manager && kustomize edit set image controller=bolingcavalry/guestbook:002 kustomize build config/default | kubectl apply -f - namespace/helloworld-system created Warning: apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16 , unavailable in v1.22 ; use apiextensions.k8s.io/v1 CustomResourceDefinition customresourcedefinition.apiextensions.k8s.io/guestbooks.webapp.com.bolingcavalry configured role.rbac.authorization.k8s.io/helloworld-leader-election-role created clusterrole.rbac.authorization.k8s.io/helloworld-manager-role created clusterrole.rbac.authorization.k8s.io/helloworld-proxy-role created Warning: rbac.authorization.k8s.io/v1beta1 ClusterRole is deprecated in v1.17 , unavailable in v1.22 ; use rbac.authorization.k8s.io/v1 ClusterRole clusterrole.rbac.authorization.k8s.io/helloworld-metrics-reader created rolebinding.rbac.authorization.k8s.io/helloworld-leader-election-rolebinding created clusterrolebinding.rbac.authorization.k8s.io/helloworld-manager-rolebinding created clusterrolebinding.rbac.authorization.k8s.io/helloworld-proxy-rolebinding created service/helloworld-controller-manager-metrics-service created deployment.apps/helloworld-controller-manager created
這時(shí)去看看kubernetes自然環(huán)境的pod,發(fā)覺(jué)的確早已增加了controller,如下圖白框:
11.仔細(xì)的您應(yīng)當(dāng)會(huì)發(fā)覺(jué)圖中黃框中表明這一pod事實(shí)上有兩個(gè)器皿,用kubectl describe指令仔細(xì)觀看,分別是kube-rbac-proxy和manager,如下圖:
11. 因?yàn)橛袃蓚€(gè)器皿,那麼查詢?nèi)罩緯r(shí)就需要特定在其中一個(gè)了,我們的controller相匹配的是manager器皿,因而查詢?nèi)罩镜闹噶钍牵?/p>
kubectl logs -f \ helloworld-controller-manager-689d4b6f5b-h9pzg \ -n helloworld-system \ -c manager
再度建立Guestbook資源的案例,依然是kubectl apply -f config/samples/指令,再去看看manager器皿的日志,由此可見(jiàn)我們改動(dòng)的信息早已打印出出來(lái):
卸載掉和清除
感受結(jié)束后,假如想把前邊建立的自然資源和CRD所有處理掉,能夠 實(shí)行下列指令:
cd $GOPATH/src/helloworld make uninstall
到此,根據(jù)kubebuilder建立Operator有關(guān)網(wǎng)絡(luò)資源的基本上步驟,我們?cè)缫迅惺苓^(guò)一遍了,這篇以了解專用工具和步驟為主導(dǎo),仍未感受到Operator實(shí)際性的強(qiáng)悍作用,這種都已非后來(lái)的章節(jié)目錄吧,我們逐漸加強(qiáng)學(xué)習(xí)實(shí)踐活動(dòng);
你并不孤單,欣宸原創(chuàng)一路相伴
Java系列
Spring系列
Docker系列產(chǎn)品
kubernetes系列
數(shù)據(jù)庫(kù)查詢 分布式數(shù)據(jù)庫(kù)系列產(chǎn)品
DevOps系列