Преглед изворни кода

support k8s deployment yaml generation (#247)

* simplify code, format makefile

* simplify code

* some optimize by kevwan and benying (#240)

Co-authored-by: 杨志泉 <zhiquan.yang@yiducloud.cn>

* 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: 杨志泉 <zhiquan.yang@yiducloud.cn>
Co-authored-by: bittoy <bittoy@qq.com>
Kevin Wan пре 3 година
родитељ
комит
7a82cf80ce

+ 3 - 3
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{
 			{

+ 2 - 2
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),
 	}
-}
+}

+ 2 - 2
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),
 	}
-}
+}

+ 5 - 0
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=

+ 1 - 1
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
 )

+ 2 - 0
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=

+ 2 - 2
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")
 	}
 

+ 19 - 1
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) {

+ 89 - 0
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",

+ 0 - 130
tools/goctl/k8s/deployment.go

@@ -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}}
-`

+ 0 - 46
tools/goctl/k8s/job.go

@@ -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
-`

+ 0 - 103
tools/goctl/k8s/kube.go

@@ -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
-}

+ 117 - 0
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
+`

+ 39 - 0
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
+`

+ 104 - 0
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,
+	})
+}

+ 4 - 0
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
 	}

+ 27 - 0
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
+}