Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/api-change-vks-fxtero00.json
Original file line number Diff line number Diff line change
@@ -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"
}
88 changes: 16 additions & 72 deletions docs/commands/vks/create-cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -19,30 +19,17 @@ grn vks create-cluster
--network-type <value>
--vpc-id <value>
--subnet-id <value>
--node-group-name <value>
--flavor-id <value>
[--os <value>]
--disk-type <value>
--ssh-key-id <value>
[--cidr <value>]
[--description <value>]
[--private-cluster <enabled|disabled>]
[--release-channel <value>]
[--load-balancer-plugin <enabled|disabled>]
[--block-store-csi-plugin <enabled|disabled>]
[--disk-size <value>]
[--num-nodes <value>]
[--private-nodes <enabled|disabled>]
[--security-groups <value>]
[--labels <value>]
[--taints <value>]
[--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.

Expand All @@ -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`).
Expand All @@ -76,60 +63,20 @@ 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 \
--name my-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):
Expand All @@ -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 <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):
Expand All @@ -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
```
90 changes: 9 additions & 81 deletions go/cmd/vks/create_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import (
"fmt"
"os"
"regexp"
"strconv"

"github.com/spf13/cobra"
)

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() {
Expand All @@ -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)
}

Expand All @@ -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")
}

Expand All @@ -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
Expand All @@ -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,
Expand All @@ -138,7 +81,6 @@ func runCreateCluster(cmd *cobra.Command, args []string) error {
"enabledLoadBalancerPlugin": enabledLBPlugin,
"enabledServiceEndpoint": false,
"azStrategy": "SINGLE",
"nodeGroups": []interface{}{nodeGroup},
}

if cidr != "" {
Expand All @@ -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)
Expand All @@ -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

Expand All @@ -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 {
Expand Down
Loading