Autoscaler for appfile (#510)

* Autoscaler for appfile

completed spec.extension.template to support autoscale in
cli and appfile

* add alias name  to cpuRequest in Cli for deploying webservice
This commit is contained in:
Zheng Xi Zhou
2020-11-07 07:54:59 +08:00
committed by GitHub
parent 37bbc37fa2
commit 91f47586cf
14 changed files with 437 additions and 126 deletions
+9 -4
View File
@@ -85,6 +85,7 @@ type Parameter struct {
Default interface{} `json:"default,omitempty"`
Usage string `json:"usage,omitempty"`
Type cue.Kind `json:"type,omitempty"`
Alias string `json:"alias,omitempty"`
}
// ConvertTemplateJSON2Object convert spec.extension to object
@@ -105,6 +106,10 @@ func ConvertTemplateJSON2Object(in *runtime.RawExtension) (Capability, error) {
}
func SetFlagBy(flags *pflag.FlagSet, v Parameter) {
name := v.Name
if v.Alias != "" {
name = v.Alias
}
switch v.Type {
case cue.IntKind:
var vv int64
@@ -118,11 +123,11 @@ func SetFlagBy(flags *pflag.FlagSet, v Parameter) {
case float64:
vv = int64(val)
}
flags.Int64P(v.Name, v.Short, vv, v.Usage)
flags.Int64P(name, v.Short, vv, v.Usage)
case cue.StringKind:
flags.StringP(v.Name, v.Short, v.Default.(string), v.Usage)
flags.StringP(name, v.Short, v.Default.(string), v.Usage)
case cue.BoolKind:
flags.BoolP(v.Name, v.Short, v.Default.(bool), v.Usage)
flags.BoolP(name, v.Short, v.Default.(bool), v.Usage)
case cue.NumberKind, cue.FloatKind:
var vv float64
switch val := v.Default.(type) {
@@ -135,7 +140,7 @@ func SetFlagBy(flags *pflag.FlagSet, v Parameter) {
case float64:
vv = val
}
flags.Float64P(v.Name, v.Short, vv, v.Usage)
flags.Float64P(name, v.Short, vv, v.Usage)
}
}
@@ -5,7 +5,7 @@ metadata:
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Autoscaler
definition.oam.dev/description: "Automatically scale workloads by cron or resource utilization"
definition.oam.dev/description: "Automatically scale workloads"
spec:
appliesToWorkloads:
- webservice
@@ -18,32 +18,56 @@ spec:
extension:
template: |
output: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Autoscaler"
spec: {
minReplicas: parameter.minReplicas
maxReplicas: parameter.maxReplicas
triggers: [{
name: parameter.name
type: parameter.type
condition: {
startAt: parameter.startAt
duration: parameter.duration
days: parameter.days
replicas: parameter.replicas
timezone: parameter.timezone
}
}, ...]
}
}
parameter: {
minReplicas: *1 | int
maxReplicas: *4 | int
name: *"" | string
type: *"cron" | string
startAt: string
duration: string
days: string
replicas: *"2" | string
timezone: *"Asia/Shanghai" | string
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Autoscaler"
spec: {
minReplicas: parameter.min
maxReplicas: parameter.max
if parameter["cpu"] != _|_ && parameter["cron"] != _|_ {
triggers: [cpuScaler, cronScaler]
}
if parameter["cpu"] != _|_ && parameter["cron"] == _|_ {
triggers: [cpuScaler]
}
if parameter["cpu"] == _|_ && parameter["cron"] != _|_ {
triggers: [cronScaler]
}
}
}
cpuScaler: {
type: "cpu"
condition: {
type: "Utilization"
if parameter["cpu"] != _|_ {
value: parameter.cpu
}
}
}
cronScaler: {
type: "cron"
if parameter["cron"] != _|_ {
condition: parameter.cron
}
}
parameter: {
// +usage=minimal replicas of the workload
min: int
// +usage=maximal replicas of the workload
max: int
// +usage=specify the value for CPU utilization, like 80, which means 80%
cpu?: string
// +usage=just for `appfile`, not available for Cli usage
cron?: {
startAt: string
duration: string
// +usage=several workdays or weekends, like "Monday, Tuesday"
days: string
replicas: string
// +usage=timezone, like "America/Seattle"
timezone: string
}
}
@@ -49,8 +49,17 @@ spec:
ports: [{
containerPort: parameter.port
}]
if parameter["cpuRequests"] != _|_ {
resources: {
limits:
cpu: parameter.cpuRequests
requests:
cpu: parameter.cpuRequests
}
}
}]
}
}
}
}
}
@@ -75,5 +84,8 @@ spec:
}
}
}]
// +usage=CPU core requests for the workload
// +alias=cpu-requests
cpuRequests?: string
}
+1 -1
View File
@@ -26,5 +26,5 @@ data:
"urL": "https://kedacore.github.io/charts",
"name": "keda",
"namespace": "keda",
"version": "2.0.0-beta1.2"
"version": "2.0.0-rc3"
}
+211 -41
View File
@@ -1,65 +1,235 @@
# Automatically scale workload by cron or resource utilization
# Automatically scale workloads by resource utilization metrics and cron
## Prerequisites
- [ ] [KEDA v2.0 Beta](https://keda.sh/blog/keda-2.0-beta/)
KEDA will be automatically deployed during vela installation, so just run the following command.
```shell
$ vela install
```
## Scale an application
Contents:
- [Scale by CPU resource utilization metrics](#Scale by CPU resource utilization metrics)
- [Scale workload by cron](#Scale workload by cron)
## Scale by CPU resource utilization metrics
Introduce how to automatically scale workloads by resource utilization metrics in Cli. Currently, only cpu utilization
is supported.
- Deploy an application
Run the following command to deploy application `helloworld`.
```
$ vela svc deploy frontend -t webservice -a helloworld --image nginx:1.9.2 --port 80
$ vela svc deploy frontend -t webservice -a helloworld --image nginx:1.9.2 --port 80 --cpu-requests=0.05
App helloworld deployed
```
Check the replicas of Deployment `frontend` which is deployed by workload webservice `helloworld` and there is one replica.
(TODO: The command below needs to be replaced with `vela show` to check the replicas.)
```
$ kubectl get deploy frontend
NAME READY UP-TO-DATE AVAILABLE AGE
frontend 1/1 1 1 2m52s
```
By default, the replicas of the workload webservice `helloworld` is.
- Scale the application by `cron`
- Scale the application by CPU utilization metrics
```
$ vela autoscale helloworld --svc frontend --minReplicas 1 --maxReplicas 4 --replicas 2 --name cron-test --startAt 21:00 --duration 2h --days "Monday, Tuesday"
$ vela autoscale helloworld --svc frontend --min 1 --max 5 --cpu 5
Adding autoscale for app frontend
Deploying ...
Checking Status ...
✅ Application Deployed Successfully!
- Name: frontend
Type: webservice
HEALTHY Ready: 1/1
Last Deployment:
Created at: 2020-11-03 20:53:50 +0800 CST
Updated at: 2020-11-03T21:01:20+08:00
Traits:
- autoscale:
maxReplicas=4
minReplicas=1
replicas=2
startAt=21:00
timezone=Asia/Shanghai
days=Monday, Tuesday
name=cron-test
type=cron
duration=2h
- autoscale: type: cpu minReplicas: 1 maxReplicas: 5 CPUUtilization(target/current): 5%/0% replicas: 0
Last Deployment:
Created at: 2020-11-06 16:10:54 +0800 CST
Updated at: 2020-11-06T16:19:04+08:0
```
The time is `21:07` which is in the active period of the trait which started at `21:00` and the duration is two hours.
Check the replicas of Deployment `frontend` again, it has been scaled to 2.
- Access the application with heavy requests
```
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
frontend 2/2 2 2 8m42s
$ vela port-forward helloworld 80
Forwarding from 127.0.0.1:80 -> 80
Forwarding from [::1]:80 -> 80
Forward successfully! Opening browser ...
Handling connection for 80
Handling connection for 80
Handling connection for 80
Handling connection for 80
```
On your macOS, you might need to add `sudo` ahead of the command.
- Use Apache HTTP server benchmarking tool `ab` to access the application.
```
$ ab -n 10000 -c 200 http://127.0.0.1/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
```
Monitor the replicas of the workload, and its replicas gradually increase from one to four.
```
$ vela status helloworld --svc frontend
About:
Name: helloworld
Namespace: default
Created at: 2020-11-05 20:07:21.830118 +0800 CST
Updated at: 2020-11-05 20:50:42.664725 +0800 CST
Services:
- Name: frontend
Type: webservice
HEALTHY Ready: 1/1
Traits:
- ✅ autoscale: type: cpu minReplicas: 1 maxReplicas: 5 CPUUtilization(target/current): 5%/10% replicas: 2
Last Deployment:
Created at: 2020-11-05 20:07:23 +0800 CST
Updated at: 2020-11-05T20:50:42+08:00
```
```
$ vela status helloworld --svc frontend
About:
Name: helloworld
Namespace: default
Created at: 2020-11-05 20:07:21.830118 +0800 CST
Updated at: 2020-11-05 20:50:42.664725 +0800 CST
Services:
- Name: frontend
Type: webservice
HEALTHY Ready: 1/1
Traits:
- ✅ autoscale: type: cpu minReplicas: 1 maxReplicas: 5 CPUUtilization(target/current): 5%/14% replicas: 4
Last Deployment:
Created at: 2020-11-05 20:07:23 +0800 CST
Updated at: 2020-11-05T20:50:42+08:00
```
Stop `ab` tool, and the replicas will decrease to one eventually.
## Scale workload by cron
Introduce how to automatically scale workloads by cron in Appfile.
- Prepare Appfile
Follow the instructions of [appfile](./devex/appfile.md) to prepare the `vela.yaml` as below.
```
name: testapp
services:
express-server:
# this image will be used in both build and deploy steps
image: zzxwill/kubevela-appfile-demo:v1
build:
# Here more runtime specific build templates will be supported, like NodeJS, Go, Python, Ruby.
docker:
file: Dockerfile
context: .
cmd: ["node", "server.js"]
port: 8080
autoscale:
minReplicas: 1
maxReplicas: 4
cron:
startAt: "14:00"
duration: "2h"
days: "Monday, Thursday"
replicas: "2"
timezone: "America/Seattle"
```
- Deploy an application
Run the following command to deploy the application defined in `vela.yaml`.
```
$ vela up
Parsing vela.yaml ...
Loading templates ...
Building service (express-server)...
#2 [internal] load build definition from Dockerfile
#2 sha256:c25a03ff9861be1da16a316055d11b83778efa23c655d0e69a902487bbf3c303
#2 transferring dockerfile: 37B 0.0s done
#2 DONE 0.1s
...
pushing image (zzxwill/kubevela-appfile-demo:v1)...
The push refers to repository [docker.io/zzxwill/kubevela-appfile-demo]
1893e9ad9204: Preparing
b60a6f0fd043: Preparing
...
89ae5c4ee501: Layer already exists
b60a6f0fd043: Layer already exists
1893e9ad9204: Pushed
v1: digest: sha256:11e48ce2205a1d92c1c920b3a3f41d3ee357fa2794261dc0d0e8010068e68da6 size: 1365
Rendering configs for service (express-server)...
Writing deploy config to (.vela/deploy.yaml)
Applying deploy configs ...
Checking if app has been deployed...
App has not been deployed, creating a new deployment...
✅ App has been deployed 🚀🚀🚀
Port forward: vela port-forward testapp
SSH: vela exec testapp
Logging: vela logs testapp
App status: vela status testapp
Service status: vela status testapp --svc express-server
```
- Check the replicas and wait for the scaling to take effect
Check the replicas of the application, there is one replica.
```
$ vela status testapp
About:
Name: testapp
Namespace: default
Created at: 2020-11-05 17:09:02.426632 +0800 CST
Updated at: 2020-11-05 17:09:02.426632 +0800 CST
Services:
- Name: express-server
Type: webservice
HEALTHY Ready: 1/1
Traits:
- ✅ autoscale: type: cron minReplicas: 1 maxReplicas: 4 replicas: 1
Last Deployment:
Created at: 2020-11-05 17:09:03 +0800 CST
Updated at: 2020-11-05T17:09:02+08:00
```
Wait till the time clocks `startAt`, and check again. The replicas become to two, which is specified as
`replicas` in `vela.yaml`.
```
$ vela status testapp
About:
Name: testapp
Namespace: default
Created at: 2020-11-05 17:09:02.426632 +0800 CST
Updated at: 2020-11-05 17:09:02.426632 +0800 CST
Services:
- Name: express-server
Type: webservice
HEALTHY Ready: 1/1
Traits:
- ✅ autoscale: type: cron minReplicas: 1 maxReplicas: 4 replicas: 2
Last Deployment:
Created at: 2020-11-05 17:09:03 +0800 CST
Updated at: 2020-11-05T17:09:02+08:00
```
Wait after the period ends, the replicas will be one eventually.
+4
View File
@@ -307,6 +307,10 @@ var (
q: "specify port for container ",
a: "8080",
},
{
q: "CPU core requests for the workload ",
a: "",
},
}
for _, qa := range data {
_, err := c.ExpectString(qa.q)
+10
View File
@@ -36,6 +36,16 @@ services:
# scheme: "http"
# enabled: true
# autoscale:
# minReplicas: 1
# maxReplicas: 4
# cron:
# startAt: "14:00"
# duration: "2h"
# days: "Monday, Thursday"
# replicas: "2"
# timezone: "America/Seattle"
# pi:
# image: perl
# cmd: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
+45 -22
View File
@@ -2,29 +2,52 @@ output: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Autoscaler"
spec: {
minReplicas: parameter.minReplicas
maxReplicas: parameter.maxReplicas
triggers: [{
name: parameter.name
type: parameter.type
condition: {
startAt: parameter.startAt
duration: parameter.duration
days: parameter.days
replicas: parameter.replicas
timezone: parameter.timezone
}
}, ...]
minReplicas: parameter.min
maxReplicas: parameter.max
if parameter["cpu"] != _|_ && parameter["cron"] != _|_ {
triggers: [cpuScaler, cronScaler]
}
if parameter["cpu"] != _|_ && parameter["cron"] == _|_ {
triggers: [cpuScaler]
}
if parameter["cpu"] == _|_ && parameter["cron"] != _|_ {
triggers: [cronScaler]
}
}
}
cpuScaler: {
type: "cpu"
condition: {
type: "Utilization"
if parameter["cpu"] != _|_ {
value: parameter.cpu
}
}
}
cronScaler: {
type: "cron"
if parameter["cron"] != _|_ {
condition: parameter.cron
}
}
parameter: {
minReplicas: *1 | int
maxReplicas: *4 | int
name: *"" | string
type: *"cron" | string
startAt: string
duration: string
days: string
replicas: *"2" | string
timezone: *"Asia/Shanghai" | string
// +usage=minimal replicas of the workload
min: int
// +usage=maximal replicas of the workload
max: int
// +usage=specify the value for CPU utilization, like 80, which means 80%
cpu?: string
// +usage=just for `appfile`, not available for Cli usage
cron?: {
startAt: string
duration: string
// +usage=several workdays or weekends, like "Monday, Tuesday"
days: string
replicas: string
// +usage=timezone, like "America/Seattle"
timezone: string
}
}
+13 -1
View File
@@ -36,8 +36,17 @@ output: {
ports: [{
containerPort: parameter.port
}]
if parameter["cpuRequests"] != _|_ {
resources: {
limits:
cpu: parameter.cpuRequests
requests:
cpu: parameter.cpuRequests
}
}
}]
}
}
}
}
}
@@ -62,4 +71,7 @@ parameter: {
}
}
}]
// +usage=CPU core requests for the workload
// +alias=cpu-requests
cpuRequests?: string
}
@@ -5,7 +5,7 @@ metadata:
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Autoscaler
definition.oam.dev/description: "Automatically scale workloads by cron or resource utilization"
definition.oam.dev/description: "Automatically scale workloads"
spec:
appliesToWorkloads:
- webservice
@@ -16,4 +16,4 @@ spec:
definitionRef:
name: autoscalers.standard.oam.dev
extension:
template: |
template: |
@@ -6,4 +6,5 @@ import (
const (
CronType v1alpha1.TriggerType = "cron"
CPUType v1alpha1.TriggerType = "cpu"
)
+12 -6
View File
@@ -9,9 +9,8 @@ import (
"cuelang.org/go/cue"
cueJson "cuelang.org/go/pkg/encoding/json"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/oam-dev/kubevela/api/types"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// OutputFieldName is the name of the struct contains the CR data
@@ -96,6 +95,7 @@ func GetParameters(templatePath string) ([]types.Parameter, error) {
continue
}
var param = types.Parameter{
Name: fi.Name,
Required: !fi.IsOptional,
}
@@ -109,7 +109,8 @@ func GetParameters(templatePath string) ([]types.Parameter, error) {
if param.Default == nil {
param.Default = getDefaultByKind(param.Type)
}
param.Short, param.Usage = RetrieveComments(val)
param.Short, param.Usage, param.Alias = RetrieveComments(val)
params = append(params, param)
}
return params, nil
@@ -159,10 +160,12 @@ func GetDefault(val cue.Value) interface{} {
const (
UsagePrefix = "+usage="
ShortPrefix = "+short="
// AliasPrefix is an alias of the name of a parameter element, in order to making it more friendly to Cli users
AliasPrefix = "+alias="
)
func RetrieveComments(value cue.Value) (string, string) {
var short, usage string
func RetrieveComments(value cue.Value) (string, string, string) {
var short, usage, alias string
docs := value.Doc()
for _, doc := range docs {
lines := strings.Split(doc.Text(), "\n")
@@ -176,7 +179,10 @@ func RetrieveComments(value cue.Value) (string, string) {
if strings.HasPrefix(line, UsagePrefix) {
usage = strings.TrimPrefix(line, UsagePrefix)
}
if strings.HasPrefix(line, AliasPrefix) {
alias = strings.TrimPrefix(line, AliasPrefix)
}
}
}
return short, usage
return short, usage, alias
}
+49 -9
View File
@@ -5,18 +5,16 @@ import (
"encoding/json"
"fmt"
"k8s.io/api/networking/v1beta1"
v1 "k8s.io/api/core/v1"
"github.com/oam-dev/kubevela/api/v1alpha1"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
"github.com/oam-dev/kubevela/api/v1alpha1"
"github.com/oam-dev/kubevela/pkg/application"
autoscalers "github.com/oam-dev/kubevela/pkg/controller/v1alpha1/autoscaler"
v12 "k8s.io/api/autoscaling/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/api/networking/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -33,7 +31,12 @@ func GetChecker(traitType string, c client.Client) Checker {
return &RouteChecker{c: c}
case "metric":
return &MetricChecker{c: c}
case "autoscale":
return &AutoscalerChecker{c: c}
case "cronscale":
return &AutoscalerChecker{c: c}
}
return &DefaultChecker{c: c}
}
@@ -132,6 +135,43 @@ func (d *RouteChecker) Check(ctx context.Context, reference runtimev1alpha1.Type
return StatusDone, message, nil
}
type AutoscalerChecker struct {
c client.Client
}
func (d *AutoscalerChecker) Check(ctx context.Context, ref runtimev1alpha1.TypedReference, _ string, appConfig *v1alpha2.ApplicationConfiguration, _ *application.Application) (CheckStatus, string, error) {
traitName := ref.Name
var scaler v1alpha1.Autoscaler
if err := d.c.Get(ctx, client.ObjectKey{Namespace: appConfig.Namespace, Name: traitName}, &scaler); err != nil {
return StatusChecking, "", err
}
var scalerType string
triggers := scaler.Spec.Triggers
if len(triggers) >= 1 {
scalerType = string(triggers[0].Type)
}
hpaName := "keda-hpa-" + traitName
var hpa v12.HorizontalPodAutoscaler
if err := d.c.Get(ctx, client.ObjectKey{Namespace: appConfig.Namespace, Name: hpaName}, &hpa); err != nil {
return StatusChecking, "", err
}
message := fmt.Sprintf("type: %s\tminReplicas: %v\tmaxReplicas: %v\t", scalerType, *hpa.Spec.MinReplicas,
hpa.Spec.MaxReplicas)
if scalerType == string(autoscalers.CPUType) {
// When attaching trait, and before the scaler trait works, `CurrentCPUUtilizationPercentage` is nil
currentCPUUtilizationPercentage := hpa.Status.CurrentCPUUtilizationPercentage
var zeroPercentage int32 = 0
if currentCPUUtilizationPercentage == nil {
currentCPUUtilizationPercentage = &zeroPercentage
}
message += fmt.Sprintf("CPUUtilization(target/current): %v%%/%v%%\t", *hpa.Spec.TargetCPUUtilizationPercentage,
*currentCPUUtilizationPercentage)
}
message += fmt.Sprintf("replicas: %v", hpa.Status.CurrentReplicas)
return StatusDone, message, nil
}
func GetUnstructured(ctx context.Context, c client.Client, ns string, resourceRef runtimev1alpha1.TypedReference) (*unstructured.Unstructured, error) {
resource := unstructured.Unstructured{}
resource.SetGroupVersionKind(resourceRef.GroupVersionKind())
+14 -10
View File
@@ -6,13 +6,12 @@ import (
"strconv"
"strings"
"cuelang.org/go/cue"
"github.com/oam-dev/kubevela/api/types"
"github.com/oam-dev/kubevela/pkg/application"
"github.com/oam-dev/kubevela/pkg/commands/util"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/oam-dev/kubevela/pkg/plugins"
"cuelang.org/go/cue"
"github.com/spf13/pflag"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -62,30 +61,35 @@ func BaseComplete(envName string, workloadName string, appName string, flagSet *
}
for _, v := range template.Parameters {
name := v.Name
if v.Alias != "" {
name = v.Alias
}
// Cli can check required flag before make a request to backend, but API itself could not, so validate flags here
flag := flagSet.Lookup(v.Name)
if v.Name == "name" {
flag := flagSet.Lookup(name)
if name == "name" {
continue
}
if flag == nil || flag.Value.String() == "" {
if v.Required {
return nil, fmt.Errorf("required flag(s) \"%s\" not set", v.Name)
return nil, fmt.Errorf("required flag(s) \"%s\" not set", name)
}
continue
}
switch v.Type {
case cue.IntKind:
workloadData[v.Name], err = flagSet.GetInt64(v.Name)
workloadData[v.Name], err = flagSet.GetInt64(name)
case cue.StringKind:
workloadData[v.Name], err = flagSet.GetString(v.Name)
workloadData[v.Name], err = flagSet.GetString(name)
case cue.BoolKind:
workloadData[v.Name], err = flagSet.GetBool(v.Name)
workloadData[v.Name], err = flagSet.GetBool(name)
case cue.NumberKind, cue.FloatKind:
workloadData[v.Name], err = flagSet.GetFloat64(v.Name)
workloadData[v.Name], err = flagSet.GetFloat64(name)
}
if err != nil {
if strings.Contains(err.Error(), "of flag of type string") {
data, _ := flagSet.GetString(v.Name)
data, _ := flagSet.GetString(name)
switch v.Type {
case cue.IntKind:
workloadData[v.Name], err = strconv.ParseInt(data, 10, 64)