diff --git a/.changes/next-release/api-change-vks-fxtero00.json b/.changes/next-release/api-change-vks-fxtero00.json new file mode 100644 index 0000000..8afb8cb --- /dev/null +++ b/.changes/next-release/api-change-vks-fxtero00.json @@ -0,0 +1,5 @@ +{ + "type": "api-change", + "category": "vks", + "description": "create-cluster now provisions the control plane only and no longer creates a default node group: removed --node-group-name/--flavor-id/--os/--disk-type/--ssh-key-id/--disk-size/--num-nodes/--private-nodes/--security-groups/--labels/--taints. Create workers separately with create-nodegroup" +} diff --git a/docs/commands/vks/create-cluster.md b/docs/commands/vks/create-cluster.md index 0bb22b6..c2d0828 100644 --- a/docs/commands/vks/create-cluster.md +++ b/docs/commands/vks/create-cluster.md @@ -2,9 +2,9 @@ ## Description -Create a new VKS cluster with an initial default node group. The command provisions both the control plane and the first node group in a single call. +Create a new VKS cluster (control plane only). This command provisions the cluster itself; add worker nodes afterwards with [create-nodegroup](create-nodegroup.md). -Cluster names must be 5–20 characters, lowercase alphanumeric and hyphens, starting and ending with an alphanumeric character. Node group names follow the same pattern with a length of 5–15 characters. +Cluster names must be 5–20 characters, lowercase alphanumeric and hyphens, starting and ending with an alphanumeric character. When `--network-type` is `TIGERA` or `CILIUM_OVERLAY`, the `--cidr` option is required. By default, both the load balancer plugin and the block store CSI plugin are enabled; use `--load-balancer-plugin disabled` or `--block-store-csi-plugin disabled` to turn them off. @@ -19,30 +19,17 @@ grn vks create-cluster --network-type --vpc-id --subnet-id - --node-group-name - --flavor-id - [--os ] - --disk-type - --ssh-key-id [--cidr ] [--description ] [--private-cluster ] [--release-channel ] [--load-balancer-plugin ] [--block-store-csi-plugin ] - [--disk-size ] - [--num-nodes ] - [--private-nodes ] - [--security-groups ] - [--labels ] - [--taints ] [--dry-run] ``` ## Options -**Cluster settings** - `--name` (required) : Cluster name. Must be 5–20 characters, lowercase alphanumeric and hyphens, starting and ending with an alphanumeric character. @@ -56,7 +43,7 @@ grn vks create-cluster : VPC ID where the cluster will be provisioned. `--subnet-id` (required) -: Subnet ID for the cluster control plane and the default node group. +: Subnet ID for the cluster control plane. `--cidr` (optional) : Pod CIDR block. Required when `--network-type` is `TIGERA` or `CILIUM_OVERLAY` (e.g. `10.96.0.0/12`). @@ -76,47 +63,12 @@ grn vks create-cluster `--block-store-csi-plugin` (optional, default `enabled`) : Block store CSI plugin state. Accepted values: `enabled`, `disabled`. -**Node group settings** - -`--node-group-name` (required) -: Name of the initial node group. Must be 5–15 characters, lowercase alphanumeric and hyphens, starting and ending with an alphanumeric character. - -`--flavor-id` (required) -: Flavor (instance type) ID for the nodes. - -`--os` (optional, default `ubuntu`) -: Node group OS image. Supported values: `ubuntu`, `linux`, `rocky`. - -`--disk-type` (required) -: Disk type ID for the node boot volumes. - -`--ssh-key-id` (required) -: SSH key pair ID to inject into each node. - -`--disk-size` (optional) -: Boot disk size in GiB. Accepted range: 20–5000. Default: `100`. - -`--num-nodes` (optional) -: Number of nodes to create in the default node group. Accepted range: 0–10. Default: `1`. - -`--private-nodes` (optional, default `disabled`) -: Private nodes state. `enabled` means nodes will not have public IP addresses. Accepted values: `enabled`, `disabled`. - -`--security-groups` (optional) -: Comma-separated list of security group IDs to attach to the nodes (e.g. `sg-aaa111,sg-bbb222`). - -`--labels` (optional) -: Comma-separated `key=value` pairs to add as Kubernetes node labels (e.g. `env=prod,tier=app`). - -`--taints` (optional) -: Comma-separated node taints in `key=value:effect` format (e.g. `dedicated=gpu:NoSchedule`). - `--dry-run` (optional) : Validate all parameters and print a report without sending the create request. ## Examples -Create a minimal cluster with CILIUM_NATIVE_ROUTING: +Create a cluster with CILIUM_NATIVE_ROUTING: ```bash grn vks create-cluster \ @@ -124,12 +76,7 @@ grn vks create-cluster \ --k8s-version v1.29.1 \ --network-type CILIUM_NATIVE_ROUTING \ --vpc-id net-abc12345-0000-0000-0000-000000000001 \ - --subnet-id sub-abc12345-0000-0000-0000-000000000001 \ - --node-group-name default-ng \ - --flavor-id flv-2c4g \ - --os ubuntu \ - --disk-type SSD \ - --ssh-key-id key-abc12345-0000-0000-0000-000000000001 + --subnet-id sub-abc12345-0000-0000-0000-000000000001 ``` Create a cluster with TIGERA network type (CIDR required): @@ -141,16 +88,18 @@ grn vks create-cluster \ --network-type TIGERA \ --cidr 10.96.0.0/12 \ --vpc-id net-abc12345-0000-0000-0000-000000000001 \ - --subnet-id sub-abc12345-0000-0000-0000-000000000001 \ - --node-group-name prod-ng \ - --flavor-id flv-4c8g \ - --os ubuntu \ + --subnet-id sub-abc12345-0000-0000-0000-000000000001 +``` + +Then add a node group: + +```bash +grn vks create-nodegroup \ + --cluster-id \ + --name default-ng \ + --flavor-id flv-2c4g \ --disk-type SSD \ - --disk-size 200 \ - --num-nodes 3 \ - --ssh-key-id key-abc12345-0000-0000-0000-000000000001 \ - --labels env=prod,tier=app \ - --taints dedicated=gpu:NoSchedule + --ssh-key-id key-abc12345-0000-0000-0000-000000000001 ``` Validate parameters without creating (dry run): @@ -162,10 +111,5 @@ grn vks create-cluster \ --network-type CILIUM_NATIVE_ROUTING \ --vpc-id net-abc12345-0000-0000-0000-000000000001 \ --subnet-id sub-abc12345-0000-0000-0000-000000000001 \ - --node-group-name default-ng \ - --flavor-id flv-2c4g \ - --os ubuntu \ - --disk-type SSD \ - --ssh-key-id key-abc12345-0000-0000-0000-000000000001 \ --dry-run ``` diff --git a/go/cmd/vks/create_cluster.go b/go/cmd/vks/create_cluster.go index 51f1476..29b8de5 100644 --- a/go/cmd/vks/create_cluster.go +++ b/go/cmd/vks/create_cluster.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "regexp" - "strconv" "github.com/spf13/cobra" ) @@ -12,7 +11,9 @@ import ( var createClusterCmd = &cobra.Command{ Use: "create-cluster", Short: "Create a new VKS cluster", - RunE: runCreateCluster, + Long: "Create a new VKS cluster (control plane only). " + + "Add worker nodes afterwards with 'grn vks create-nodegroup'.", + RunE: runCreateCluster, } func init() { @@ -23,14 +24,8 @@ func init() { f.String("network-type", "", "Network type: TIGERA, CILIUM_OVERLAY, CILIUM_NATIVE_ROUTING (required)") f.String("vpc-id", "", "VPC ID (required)") f.String("subnet-id", "", "Subnet ID (required)") - // Node group settings (required) - f.String("node-group-name", "", "Default node group name (required)") - f.String("flavor-id", "", "Flavor ID for node group (required)") - f.String("os", "ubuntu", "Node group OS image (ubuntu, linux, rocky)") - f.String("disk-type", "", "Disk type ID (required)") - f.String("ssh-key-id", "", "SSH key ID for node group (required)") - - for _, name := range []string{"name", "k8s-version", "network-type", "vpc-id", "subnet-id", "node-group-name", "flavor-id", "disk-type", "ssh-key-id"} { + + for _, name := range []string{"name", "k8s-version", "network-type", "vpc-id", "subnet-id"} { createClusterCmd.MarkFlagRequired(name) } @@ -41,14 +36,6 @@ func init() { f.String("release-channel", "STABLE", "Release channel (RAPID, STABLE)") f.String("load-balancer-plugin", "enabled", "Load balancer plugin (enabled, disabled)") f.String("block-store-csi-plugin", "enabled", "Block store CSI plugin (enabled, disabled)") - - // Node group settings (optional) - f.Int("disk-size", 100, "Disk size in GiB (20-5000)") - f.Int("num-nodes", 1, "Number of nodes (0-10)") - f.String("private-nodes", "disabled", "Private nodes (enabled, disabled)") - f.String("security-groups", "", "Security group IDs (comma-separated)") - f.String("labels", "", "Node labels as key=value pairs (comma-separated)") - f.String("taints", "", "Node taints as key=value:effect (comma-separated)") f.Bool("dry-run", false, "Validate parameters without creating the cluster") } @@ -61,32 +48,16 @@ func runCreateCluster(cmd *cobra.Command, args []string) error { cidr, _ := cmd.Flags().GetString("cidr") description, _ := cmd.Flags().GetString("description") releaseChannel, _ := cmd.Flags().GetString("release-channel") - - ngName, _ := cmd.Flags().GetString("node-group-name") - flavorID, _ := cmd.Flags().GetString("flavor-id") - osImage, _ := cmd.Flags().GetString("os") - diskType, _ := cmd.Flags().GetString("disk-type") - sshKeyID, _ := cmd.Flags().GetString("ssh-key-id") - diskSize, _ := cmd.Flags().GetInt("disk-size") - numNodes, _ := cmd.Flags().GetInt("num-nodes") - securityGroups, _ := cmd.Flags().GetString("security-groups") - labels, _ := cmd.Flags().GetString("labels") - taints, _ := cmd.Flags().GetString("taints") dryRun, _ := cmd.Flags().GetBool("dry-run") // Parse enabled/disabled toggle flags. privateClusterVal, _ := cmd.Flags().GetString("private-cluster") - privateNodesVal, _ := cmd.Flags().GetString("private-nodes") lbPluginVal, _ := cmd.Flags().GetString("load-balancer-plugin") csiPluginVal, _ := cmd.Flags().GetString("block-store-csi-plugin") enablePrivateCluster, err := parseToggle("private-cluster", privateClusterVal) if err != nil { return err } - enablePrivateNodes, err := parseToggle("private-nodes", privateNodesVal) - if err != nil { - return err - } enabledLBPlugin, err := parseToggle("load-balancer-plugin", lbPluginVal) if err != nil { return err @@ -96,36 +67,8 @@ func runCreateCluster(cmd *cobra.Command, args []string) error { return err } - // Build node group - nodeGroup := map[string]interface{}{ - "name": ngName, - "flavorId": flavorID, - "os": osImage, - "diskSize": diskSize, - "diskType": diskType, - "numNodes": numNodes, - "enablePrivateNodes": enablePrivateNodes, - "sshKeyId": sshKeyID, - "upgradeConfig": map[string]interface{}{ - "maxSurge": 1, - "maxUnavailable": 0, - "strategy": "SURGE", - }, - "subnetId": subnetID, - "securityGroups": []string{}, - } - - if securityGroups != "" { - nodeGroup["securityGroups"] = parseCommaSeparated(securityGroups) - } - if labels != "" { - nodeGroup["labels"] = parseLabels(labels) - } - if taints != "" { - nodeGroup["taints"] = parseTaints(taints) - } - - // Build cluster body + // Build cluster body. Node groups are created separately via + // 'grn vks create-nodegroup'. body := map[string]interface{}{ "name": name, "version": k8sVersion, @@ -138,7 +81,6 @@ func runCreateCluster(cmd *cobra.Command, args []string) error { "enabledLoadBalancerPlugin": enabledLBPlugin, "enabledServiceEndpoint": false, "azStrategy": "SINGLE", - "nodeGroups": []interface{}{nodeGroup}, } if cidr != "" { @@ -149,7 +91,7 @@ func runCreateCluster(cmd *cobra.Command, args []string) error { } if dryRun { - return validateCreateCluster(name, ngName, networkType, cidr, diskSize, numNodes) + return validateCreateCluster(name, networkType, cidr) } apiClient, err := createClient(cmd) @@ -166,9 +108,8 @@ func runCreateCluster(cmd *cobra.Command, args []string) error { return outputResult(cmd, result) } -func validateCreateCluster(name, ngName, networkType, cidr string, diskSize, numNodes int) error { +func validateCreateCluster(name, networkType, cidr string) error { clusterNameRE := regexp.MustCompile(`^[a-z0-9][a-z0-9\-]{3,18}[a-z0-9]$`) - ngNameRE := regexp.MustCompile(`^[a-z0-9][a-z0-9-]{3,13}[a-z0-9]$`) var errors []string @@ -181,19 +122,6 @@ func validateCreateCluster(name, ngName, networkType, cidr string, diskSize, num errors = append(errors, fmt.Sprintf("--cidr is required when network-type is %s", networkType)) } - if !ngNameRE.MatchString(ngName) { - errors = append(errors, fmt.Sprintf( - "Node group name '%s' is invalid. Must be 5-15 chars, lowercase alphanumeric and hyphens, start/end with alphanumeric.", ngName)) - } - - if diskSize < 20 || diskSize > 5000 { - errors = append(errors, fmt.Sprintf("Disk size %s out of range (20-5000 GiB)", strconv.Itoa(diskSize))) - } - - if numNodes < 0 || numNodes > 10 { - errors = append(errors, fmt.Sprintf("Number of nodes %s out of range (0-10)", strconv.Itoa(numNodes))) - } - fmt.Println("=== DRY RUN: Validation results ===") fmt.Println() if len(errors) > 0 {