From 7a82cf80ceac8bc5f46b81efdd7c397f04177471 Mon Sep 17 00:00:00 2001 From: Kevin Wan Date: Mon, 7 Dec 2020 00:07:50 +0800 Subject: [PATCH] support k8s deployment yaml generation (#247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * simplify code, format makefile * simplify code * some optimize by kevwan and benying (#240) Co-authored-by: 杨志泉 * optimization (#241) * optimize docker file generation, make docker build faster * support k8s deployment yaml generation Co-authored-by: benying <31179034+benyingY@users.noreply.github.com> Co-authored-by: 杨志泉 Co-authored-by: bittoy --- core/stores/redis/redis_test.go | 6 +- .../rpc/add/internal/svc/servicecontext.go | 4 +- .../rpc/check/internal/svc/servicecontext.go | 4 +- example/shorturl/go.sum | 5 + go.mod | 2 +- go.sum | 2 + tools/goctl/configgen/genconfig.go | 4 +- tools/goctl/docker/docker.go | 20 ++- tools/goctl/goctl.go | 89 ++++++++++++ tools/goctl/k8s/deployment.go | 130 ------------------ tools/goctl/k8s/job.go | 46 ------- tools/goctl/k8s/kube.go | 103 -------------- tools/goctl/kube/deployment.go | 117 ++++++++++++++++ tools/goctl/kube/job.go | 39 ++++++ tools/goctl/kube/kube.go | 104 ++++++++++++++ tools/goctl/tpl/templates.go | 4 + tools/goctl/util/path.go | 27 ++++ 17 files changed, 416 insertions(+), 290 deletions(-) delete mode 100644 tools/goctl/k8s/deployment.go delete mode 100644 tools/goctl/k8s/job.go delete mode 100644 tools/goctl/k8s/kube.go create mode 100644 tools/goctl/kube/deployment.go create mode 100644 tools/goctl/kube/job.go create mode 100644 tools/goctl/kube/kube.go diff --git a/core/stores/redis/redis_test.go b/core/stores/redis/redis_test.go index be0a8416..50bc4478 100644 --- a/core/stores/redis/redis_test.go +++ b/core/stores/redis/redis_test.go @@ -556,7 +556,7 @@ func TestRedis_SortedSet(t *testing.T) { val, err = client.Zscore("key", "value1") assert.Nil(t, err) assert.Equal(t, int64(5), val) - val, err = NewRedis(client.Addr, "").Zadds("key") + _, err = NewRedis(client.Addr, "").Zadds("key") assert.NotNil(t, err) val, err = client.Zadds("key", Pair{ Key: "value2", @@ -567,9 +567,9 @@ func TestRedis_SortedSet(t *testing.T) { }) assert.Nil(t, err) assert.Equal(t, int64(2), val) - pairs, err := NewRedis(client.Addr, "").ZRevRangeWithScores("key", 1, 3) + _, err = NewRedis(client.Addr, "").ZRevRangeWithScores("key", 1, 3) assert.NotNil(t, err) - pairs, err = client.ZRevRangeWithScores("key", 1, 3) + pairs, err := client.ZRevRangeWithScores("key", 1, 3) assert.Nil(t, err) assert.EqualValues(t, []Pair{ { diff --git a/example/bookstore/rpc/add/internal/svc/servicecontext.go b/example/bookstore/rpc/add/internal/svc/servicecontext.go index 4b698dd5..32fa9344 100755 --- a/example/bookstore/rpc/add/internal/svc/servicecontext.go +++ b/example/bookstore/rpc/add/internal/svc/servicecontext.go @@ -14,7 +14,7 @@ type ServiceContext struct { func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ - c: c, + c: c, Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), } -} \ No newline at end of file +} diff --git a/example/bookstore/rpc/check/internal/svc/servicecontext.go b/example/bookstore/rpc/check/internal/svc/servicecontext.go index 508fbdc5..d497c029 100755 --- a/example/bookstore/rpc/check/internal/svc/servicecontext.go +++ b/example/bookstore/rpc/check/internal/svc/servicecontext.go @@ -14,7 +14,7 @@ type ServiceContext struct { func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ - c: c, + c: c, Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), } -} \ No newline at end of file +} diff --git a/example/shorturl/go.sum b/example/shorturl/go.sum index 1b15bd54..eb095b94 100644 --- a/example/shorturl/go.sum +++ b/example/shorturl/go.sum @@ -37,6 +37,7 @@ github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQa github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -49,6 +50,7 @@ github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819 h1:9778zj477h/VauD8 github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819/go.mod h1:MvzMVHq8BH2Ji/o8TGDocVA70byvLrAgFTxkEnmjO4Y= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/emicklei/proto v1.9.0 h1:l0QiNT6Qs7Yj0Mb4X6dnWBQer4ebei2BFcgQLbGqUDc= github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -131,6 +133,7 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8= github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= +github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U= github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= @@ -220,6 +223,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= @@ -252,6 +256,7 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= diff --git a/go.mod b/go.mod index e02204bf..faf90e94 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( google.golang.org/protobuf v1.25.0 gopkg.in/cheggaaa/pb.v1 v1.0.28 gopkg.in/h2non/gock.v1 v1.0.15 - gopkg.in/yaml.v2 v2.3.0 + gopkg.in/yaml.v2 v2.4.0 honnef.co/go/tools v0.0.1-2020.1.4 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/go.sum b/go.sum index 4897f3bd..47b33660 100644 --- a/go.sum +++ b/go.sum @@ -452,6 +452,8 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= diff --git a/tools/goctl/configgen/genconfig.go b/tools/goctl/configgen/genconfig.go index 31ce9246..b768d8c3 100644 --- a/tools/goctl/configgen/genconfig.go +++ b/tools/goctl/configgen/genconfig.go @@ -43,8 +43,8 @@ func GenConfigCommand(c *cli.Context) error { return errors.New("abs failed: " + c.String("path")) } - goModPath, hasFound := util.FindGoModPath(path) - if !hasFound { + goModPath, found := util.FindGoModPath(path) + if !found { return errors.New("go mod not initial") } diff --git a/tools/goctl/docker/docker.go b/tools/goctl/docker/docker.go index efd62bb8..f0410b7d 100644 --- a/tools/goctl/docker/docker.go +++ b/tools/goctl/docker/docker.go @@ -2,6 +2,7 @@ package docker import ( "errors" + "fmt" "os" "path/filepath" "strings" @@ -33,12 +34,29 @@ func DockerCommand(c *cli.Context) error { return errors.New("-go can't be empty") } + if !util.FileExists(goFile) { + return fmt.Errorf("file %q not found", goFile) + } + + if _, err := os.Stat(etcDir); os.IsNotExist(err) { + return generateDockerfile(goFile) + } + cfg, err := findConfig(goFile, etcDir) if err != nil { return err } - return generateDockerfile(goFile, "-f", "etc/"+cfg) + if err := generateDockerfile(goFile, "-f", "etc/"+cfg); err != nil { + return err + } + + projDir, ok := util.FindProjectPath(goFile) + if ok { + fmt.Printf("Run \"docker build ...\" command in dir %q\n", projDir) + } + + return nil } func findConfig(file, dir string) (string, error) { diff --git a/tools/goctl/goctl.go b/tools/goctl/goctl.go index 1055d859..6f3bded8 100644 --- a/tools/goctl/goctl.go +++ b/tools/goctl/goctl.go @@ -18,6 +18,7 @@ import ( "github.com/tal-tech/go-zero/tools/goctl/api/validate" "github.com/tal-tech/go-zero/tools/goctl/configgen" "github.com/tal-tech/go-zero/tools/goctl/docker" + "github.com/tal-tech/go-zero/tools/goctl/kube" model "github.com/tal-tech/go-zero/tools/goctl/model/sql/command" rpc "github.com/tal-tech/go-zero/tools/goctl/rpc/cli" "github.com/tal-tech/go-zero/tools/goctl/tpl" @@ -198,6 +199,94 @@ var ( }, Action: docker.DockerCommand, }, + { + Name: "kube", + Usage: "generate kubernetes files", + Subcommands: []cli.Command{ + { + Name: "deploy", + Usage: "generate deployment yaml file", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "name", + Usage: "the name of deployment", + Required: true, + }, + cli.StringFlag{ + Name: "namespace", + Usage: "the namespace of deployment", + Required: true, + }, + cli.StringFlag{ + Name: "image", + Usage: "the docker image of deployment", + Required: true, + }, + cli.StringFlag{ + Name: "secret", + Usage: "the image pull secret", + Required: true, + }, + cli.IntFlag{ + Name: "requestCpu", + Usage: "the request cpu to deploy", + Value: 500, + }, + cli.IntFlag{ + Name: "requestMem", + Usage: "the request memory to deploy", + Value: 512, + }, + cli.IntFlag{ + Name: "limitCpu", + Usage: "the limit cpu to deploy", + Value: 1000, + }, + cli.IntFlag{ + Name: "limitMem", + Usage: "the limit memory to deploy", + Value: 1024, + }, + cli.StringFlag{ + Name: "o", + Usage: "the output yaml file", + Required: true, + }, + cli.IntFlag{ + Name: "replicas", + Usage: "the number of replicas to deploy", + Value: 3, + }, + cli.IntFlag{ + Name: "revisions", + Usage: "the number of revision history to limit", + Value: 5, + }, + cli.IntFlag{ + Name: "port", + Usage: "the port of the deployment to listen on pod", + Required: true, + }, + cli.IntFlag{ + Name: "nodePort", + Usage: "the nodePort of the deployment to expose", + Value: 0, + }, + cli.IntFlag{ + Name: "minReplicas", + Usage: "the min replicas to deploy", + Value: 3, + }, + cli.IntFlag{ + Name: "maxReplicas", + Usage: "the max replicas of deploy", + Value: 10, + }, + }, + Action: kube.DeploymentCommand, + }, + }, + }, { Name: "rpc", Usage: "generate rpc code", diff --git a/tools/goctl/k8s/deployment.go b/tools/goctl/k8s/deployment.go deleted file mode 100644 index 891d1cb6..00000000 --- a/tools/goctl/k8s/deployment.go +++ /dev/null @@ -1,130 +0,0 @@ -package k8s - -var apiRpcTmeplate = `apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{.name}} - namespace: {{.namespace}} - labels: - app: {{.name}} -spec: - replicas: {{.replicas}} - revisionHistoryLimit: {{.revisionHistoryLimit}} - selector: - matchLabels: - app: {{.name}} - template: - metadata: - labels: - app: {{.name}} - spec:{{if .envIsDev}} - terminationGracePeriodSeconds: 60{{end}} - containers: - - name: {{.name}} - image: registry-vpc.cn-hangzhou.aliyuncs.com/{{.namespace}}/ - lifecycle: - preStop: - exec: - command: ["sh","-c","sleep 5"] - ports: - - containerPort: {{.port}} - readinessProbe: - tcpSocket: - port: {{.port}} - initialDelaySeconds: 5 - periodSeconds: 10 - livenessProbe: - tcpSocket: - port: {{.port}} - initialDelaySeconds: 15 - periodSeconds: 20 - env: - - name: aliyun_logs_k8slog - value: "stdout" - - name: aliyun_logs_k8slog_tags - value: "stage={{.env}}" - - name: aliyun_logs_k8slog_format - value: "json" - resources: - limits: - cpu: {{.limitCpu}}m - memory: {{.limitMem}}Mi - requests: - cpu: {{.requestCpu}}m - memory: {{.requestMem}}Mi - command: - - ./{{.serviceName}} - - -f - - ./{{.name}}.json - volumeMounts: - - name: timezone - mountPath: /etc/localtime - imagePullSecrets: - - name: {{.namespace}} - volumes: - - name: timezone - hostPath: - path: /usr/share/zoneinfo/Asia/Shanghai - ---- - -apiVersion: v1 -kind: Service -metadata: - name: {{.name}}-svc - namespace: {{.namespace}} -spec: - ports: - - nodePort: 3{{.port}} - port: {{.port}} - protocol: TCP - targetPort: {{.port}} - selector: - app: {{.name}} - sessionAffinity: None - type: NodePort{{if .envIsPreOrPro}} - ---- - -apiVersion: autoscaling/v2beta1 -kind: HorizontalPodAutoscaler -metadata: - name: {{.name}}-hpa-c - namespace: {{.namespace}} - labels: - app: {{.name}}-hpa-c -spec: - scaleTargetRef: - apiVersion: apps/v1beta1 - kind: Deployment - name: di-api - minReplicas: {{.minReplicas}} - maxReplicas: {{.maxReplicas}} - metrics: - - type: Resource - resource: - name: cpu - targetAverageUtilization: 80 - ---- - -apiVersion: autoscaling/v2beta1 -kind: HorizontalPodAutoscaler -metadata: - name: {{.name}}-hpa-m - namespace: {{.namespace}} - labels: - app: {{.name}}-hpa-m -spec: - scaleTargetRef: - apiVersion: apps/v1beta1 - kind: Deployment - name: {{.name}} - minReplicas: {{.minReplicas}} - maxReplicas: {{.maxReplicas}} - metrics: - - type: Resource - resource: - name: memory - targetAverageUtilization: 80{{end}} -` diff --git a/tools/goctl/k8s/job.go b/tools/goctl/k8s/job.go deleted file mode 100644 index db9fdea4..00000000 --- a/tools/goctl/k8s/job.go +++ /dev/null @@ -1,46 +0,0 @@ -package k8s - -var jobTmeplate = `apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: {{.name}} - namespace: {{.namespace}} -spec: - successfulJobsHistoryLimit: {{.successfulJobsHistoryLimit}} - schedule: "{{.schedule}}" - jobTemplate: - spec: - template: - spec: - containers: - - name: {{.name}} - image: registry-vpc.cn-hangzhou.aliyuncs.com/{{.namespace}}/ - env: - - name: aliyun_logs_k8slog - value: "stdout" - - name: aliyun_logs_k8slog_tags - value: "stage={{.env}}" - - name: aliyun_logs_k8slog_format - value: "json" - resources: - limits: - cpu: {{.limitCpu}}m - memory: {{.limitMem}}Mi - requests: - cpu: {{.requestCpu}}m - memory: {{.requestMem}}Mi - command: - - ./{{.serviceName}} - - -f - - ./{{.name}}.json - volumeMounts: - - name: timezone - mountPath: /etc/localtime - imagePullSecrets: - - name: {{.namespace}} - restartPolicy: OnFailure - volumes: - - name: timezone - hostPath: - path: /usr/share/zoneinfo/Asia/Shanghai -` diff --git a/tools/goctl/k8s/kube.go b/tools/goctl/k8s/kube.go deleted file mode 100644 index af065921..00000000 --- a/tools/goctl/k8s/kube.go +++ /dev/null @@ -1,103 +0,0 @@ -package k8s - -import ( - "bytes" - "errors" - "fmt" - "text/template" -) - -const ( - ServiceTypeApi ServiceType = "api" - ServiceTypeRpc ServiceType = "rpc" - ServiceTypeJob ServiceType = "job" - envDev = "dev" -) - -var errUnknownServiceType = errors.New("unknown service type") - -type ( - ServiceType string - - KubeRequest struct { - Env string - ServiceName string - ServiceType ServiceType - Namespace string - Schedule string - Replicas int - RevisionHistoryLimit int - Port int - LimitCpu int - LimitMem int - RequestCpu int - RequestMem int - SuccessfulJobsHistoryLimit int - HpaMinReplicas int - HpaMaxReplicas int - } -) - -func Gen(req KubeRequest) (string, error) { - switch req.ServiceType { - case ServiceTypeApi, ServiceTypeRpc: - return genApiRpc(req) - case ServiceTypeJob: - return genJob(req) - default: - return "", errUnknownServiceType - } -} - -func genApiRpc(req KubeRequest) (string, error) { - t, err := template.New("api_rpc").Parse(apiRpcTmeplate) - if err != nil { - return "", err - } - buffer := new(bytes.Buffer) - err = t.Execute(buffer, map[string]interface{}{ - "name": fmt.Sprintf("%s-%s", req.ServiceName, req.ServiceType), - "namespace": req.Namespace, - "replicas": req.Replicas, - "revisionHistoryLimit": req.RevisionHistoryLimit, - "port": req.Port, - "limitCpu": req.LimitCpu, - "limitMem": req.LimitMem, - "requestCpu": req.RequestCpu, - "requestMem": req.RequestMem, - "serviceName": req.ServiceName, - "env": req.Env, - "envIsPreOrPro": req.Env != envDev, - "envIsDev": req.Env == envDev, - "minReplicas": req.HpaMinReplicas, - "maxReplicas": req.HpaMaxReplicas, - }) - if err != nil { - return "", nil - } - return buffer.String(), nil -} - -func genJob(req KubeRequest) (string, error) { - t, err := template.New("job").Parse(jobTmeplate) - if err != nil { - return "", err - } - buffer := new(bytes.Buffer) - err = t.Execute(buffer, map[string]interface{}{ - "name": fmt.Sprintf("%s-%s", req.ServiceName, req.ServiceType), - "namespace": req.Namespace, - "schedule": req.Schedule, - "successfulJobsHistoryLimit": req.SuccessfulJobsHistoryLimit, - "limitCpu": req.LimitCpu, - "limitMem": req.LimitMem, - "requestCpu": req.RequestCpu, - "requestMem": req.RequestMem, - "serviceName": req.ServiceName, - "env": req.Env, - }) - if err != nil { - return "", nil - } - return buffer.String(), nil -} diff --git a/tools/goctl/kube/deployment.go b/tools/goctl/kube/deployment.go new file mode 100644 index 00000000..710393c6 --- /dev/null +++ b/tools/goctl/kube/deployment.go @@ -0,0 +1,117 @@ +package kube + +var deploymentTemplate = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{.Name}} + namespace: {{.Namespace}} + labels: + app: {{.Name}} +spec: + replicas: {{.Replicas}} + revisionHistoryLimit: {{.Revisions}} + selector: + matchLabels: + app: {{.Name}} + template: + metadata: + labels: + app: {{.Name}} + spec: + containers: + - name: {{.Name}} + image: {{.Image}} + lifecycle: + preStop: + exec: + command: ["sh","-c","sleep 5"] + ports: + - containerPort: {{.Port}} + readinessProbe: + tcpSocket: + port: {{.Port}} + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: {{.Port}} + initialDelaySeconds: 15 + periodSeconds: 20 + resources: + requests: + cpu: {{.RequestCpu}}m + memory: {{.RequestMem}}Mi + limits: + cpu: {{.LimitCpu}}m + memory: {{.LimitMem}}Mi + volumeMounts: + - name: timezone + mountPath: /etc/localtime + imagePullSecrets: + - name: {{.Secret}} + volumes: + - name: timezone + hostPath: + path: /usr/share/zoneinfo/Asia/Shanghai + +--- + +apiVersion: v1 +kind: Service +metadata: + name: {{.Name}}-svc + namespace: {{.Namespace}} +spec: + ports: + {{if .UseNodePort}}- nodePort: {{.NodePort}} + port: {{.Port}} + protocol: TCP + targetPort: {{.Port}} + type: NodePort{{else}}- port: {{.Port}}{{end}} + selector: + app: {{.Name}} + +--- + +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{.Name}}-hpa-c + namespace: {{.Namespace}} + labels: + app: {{.Name}}-hpa-c +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{.Name}} + minReplicas: {{.MinReplicas}} + maxReplicas: {{.MaxReplicas}} + metrics: + - type: Resource + resource: + name: cpu + targetAverageUtilization: 80 + +--- + +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{.Name}}-hpa-m + namespace: {{.Namespace}} + labels: + app: {{.Name}}-hpa-m +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{.Name}} + minReplicas: {{.MinReplicas}} + maxReplicas: {{.MaxReplicas}} + metrics: + - type: Resource + resource: + name: memory + targetAverageUtilization: 80 +` diff --git a/tools/goctl/kube/job.go b/tools/goctl/kube/job.go new file mode 100644 index 00000000..256f1138 --- /dev/null +++ b/tools/goctl/kube/job.go @@ -0,0 +1,39 @@ +package kube + +var jobTmeplate = `apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{.Name}} + namespace: {{.Namespace}} +spec: + successfulJobsHistoryLimit: {{.SuccessfulJobsHistoryLimit}} + schedule: "{{.Schedule}}" + jobTemplate: + spec: + template: + spec: + containers: + - name: {{.Name}} + image: # todo image url + resources: + requests: + cpu: {{.RequestCpu}}m + memory: {{.RequestMem}}Mi + limits: + cpu: {{.LimitCpu}}m + memory: {{.LimitMem}}Mi + command: + - ./{{.ServiceName}} + - -f + - ./{{.Name}}.yaml + volumeMounts: + - name: timezone + mountPath: /etc/localtime + imagePullSecrets: + - name: # registry secret, if no, remove this + restartPolicy: OnFailure + volumes: + - name: timezone + hostPath: + path: /usr/share/zoneinfo/Asia/Shanghai +` diff --git a/tools/goctl/kube/kube.go b/tools/goctl/kube/kube.go new file mode 100644 index 00000000..8b82c503 --- /dev/null +++ b/tools/goctl/kube/kube.go @@ -0,0 +1,104 @@ +package kube + +import ( + "errors" + "text/template" + + "github.com/tal-tech/go-zero/tools/goctl/util" + "github.com/urfave/cli" +) + +const ( + category = "kube" + deployTemplateFile = "deployment.tpl" + jobTemplateFile = "job.tpl" + basePort = 30000 + portLimit = 32767 +) + +var errUnknownServiceType = errors.New("unknown service type") + +type ( + ServiceType string + + KubeRequest struct { + Env string + ServiceName string + ServiceType ServiceType + Namespace string + Schedule string + Replicas int + RevisionHistoryLimit int + Port int + LimitCpu int + LimitMem int + RequestCpu int + RequestMem int + SuccessfulJobsHistoryLimit int + HpaMinReplicas int + HpaMaxReplicas int + } + + Deployment struct { + Name string + Namespace string + Image string + Secret string + Replicas int + Revisions int + Port int + NodePort int + UseNodePort bool + RequestCpu int + RequestMem int + LimitCpu int + LimitMem int + MinReplicas int + MaxReplicas int + } +) + +func DeploymentCommand(c *cli.Context) error { + nodePort := c.Int("nodePort") + // 0 to disable the nodePort type + if nodePort != 0 && (nodePort < basePort || nodePort > portLimit) { + return errors.New("nodePort should be between 30000 and 32767") + } + + text, err := util.LoadTemplate(category, deployTemplateFile, deploymentTemplate) + if err != nil { + return err + } + + out, err := util.CreateIfNotExist(c.String("o")) + if err != nil { + return err + } + defer out.Close() + + t := template.Must(template.New("deploymentTemplate").Parse(text)) + return t.Execute(out, Deployment{ + Name: c.String("name"), + Namespace: c.String("namespace"), + Image: c.String("image"), + Secret: c.String("secret"), + Replicas: c.Int("replicas"), + Revisions: c.Int("revisions"), + Port: c.Int("port"), + NodePort: nodePort, + UseNodePort: nodePort > 0, + RequestCpu: c.Int("requestCpu"), + RequestMem: c.Int("requestMem"), + LimitCpu: c.Int("limitCpu"), + LimitMem: c.Int("limitMem"), + MinReplicas: c.Int("minReplicas"), + MaxReplicas: c.Int("maxReplicas"), + }) +} + +func GenTemplates(_ *cli.Context) error { + return util.InitTemplates(category, map[string]string{ + deployTemplateFile: deploymentTemplate, + jobTemplateFile: jobTmeplate, + }) +} diff --git a/tools/goctl/tpl/templates.go b/tools/goctl/tpl/templates.go index 5c71893c..a07efbb2 100644 --- a/tools/goctl/tpl/templates.go +++ b/tools/goctl/tpl/templates.go @@ -7,6 +7,7 @@ import ( "github.com/tal-tech/go-zero/core/errorx" "github.com/tal-tech/go-zero/tools/goctl/api/gogen" "github.com/tal-tech/go-zero/tools/goctl/docker" + "github.com/tal-tech/go-zero/tools/goctl/kube" modelgen "github.com/tal-tech/go-zero/tools/goctl/model/sql/gen" rpcgen "github.com/tal-tech/go-zero/tools/goctl/rpc/generator" "github.com/tal-tech/go-zero/tools/goctl/util" @@ -29,6 +30,9 @@ func GenTemplates(ctx *cli.Context) error { func() error { return docker.GenTemplates(ctx) }, + func() error { + return kube.GenTemplates(ctx) + }, ); err != nil { return err } diff --git a/tools/goctl/util/path.go b/tools/goctl/util/path.go index d0f32f80..6f9e812d 100644 --- a/tools/goctl/util/path.go +++ b/tools/goctl/util/path.go @@ -80,3 +80,30 @@ func FindGoModPath(dir string) (string, bool) { } return "", false } + +func FindProjectPath(loc string) (string, bool) { + var dir string + if strings.IndexByte(loc, '/') == 0 { + dir = loc + } else { + wd, err := os.Getwd() + if err != nil { + return "", false + } + + dir = filepath.Join(wd, loc) + } + + for { + if FileExists(filepath.Join(dir, goModeIdentifier)) { + return dir, true + } + + dir = filepath.Dir(dir) + if dir == "/" { + break + } + } + + return "", false +}