Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Deployment generation with kube generate #17950

Merged
merged 1 commit into from
Apr 3, 2023
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
20 changes: 20 additions & 0 deletions cmd/podman/kube/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"fmt"
"io"
"os"
"strings"

"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/generate"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -68,10 +70,28 @@ func generateFlags(cmd *cobra.Command) {
flags.StringVarP(&generateFile, filenameFlagName, "f", "", "Write output to the specified path")
_ = cmd.RegisterFlagCompletionFunc(filenameFlagName, completion.AutocompleteDefault)

// TODO: default should be configurable in containers.conf
typeFlagName := "type"
flags.StringVarP(&generateOptions.Type, typeFlagName, "t", define.K8sKindPod, "Generate YAML for the given Kubernetes kind")
_ = cmd.RegisterFlagCompletionFunc(typeFlagName, completion.AutocompleteNone)

replicasFlagName := "replicas"
flags.Int32VarP(&generateOptions.Replicas, replicasFlagName, "r", 1, "Set the replicas number for Deployment kind")
_ = cmd.RegisterFlagCompletionFunc(replicasFlagName, completion.AutocompleteNone)

flags.SetNormalizeFunc(utils.AliasFlags)
}

func generateKube(cmd *cobra.Command, args []string) error {
typeVal, err := cmd.Flags().GetString("type")
if err != nil {
return err
}
typeVal = strings.ToLower(typeVal)
if typeVal != define.K8sKindPod && typeVal != define.K8sKindDeployment {
return fmt.Errorf("invalid type given, only supported types are pod and deployment")
}

report, err := registry.ContainerEngine().GenerateKube(registry.GetContext(), args, generateOptions)
if err != nil {
return err
Expand Down
48 changes: 47 additions & 1 deletion docs/source/markdown/podman-kube-generate.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ podman-kube-generate - Generate Kubernetes YAML based on containers, pods or vol

## DESCRIPTION
**podman kube generate** will generate Kubernetes YAML (v1 specification) from Podman containers, pods or volumes. Regardless of whether
the input is for containers or pods, Podman will always generate the specification as a Pod. The input may be in the form
the input is for containers or pods, Podman will generate the specification as a Pod by default. The input may be in the form
of one or more containers, pods or volumes names or IDs.

`Podman Containers or Pods`
Expand All @@ -34,10 +34,19 @@ Note that the generated Kubernetes YAML file can be used to re-run the deploymen

Output to the given file, instead of STDOUT. If the file already exists, `kube generate` will refuse to replace it and return an error.

#### **--replicas**, **-r**=*replica count*

The value to set `replicas` to when generating a **Deployment** kind.
Note: this can only be set with the option `--type=deployment`.

#### **--service**, **-s**

Generate a Kubernetes service object in addition to the Pods. Used to generate a Service specification for the corresponding Pod output. In particular, if the object has portmap bindings, the service specification will include a NodePort declaration to expose the service. A random port is assigned by Podman in the specification.

#### **--type**, **-t**=*pod | deployment*

The Kubernetes kind to generate in the YAML file. Currently, the only supported Kubernetes specifications are `Pod` and `Deployment`. By default, the `Pod` specification will be generated.

## EXAMPLES

Create Kubernetes Pod YAML for a container called `some-mariadb`.
Expand Down Expand Up @@ -80,6 +89,43 @@ spec:
tty: true
```

Create Kubernetes Deployment YAML with 3 replicas for a container called `dep-ctr`
```
$ podman kube generate --type deployment --replicas 3 dep-ct
r
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-4.5.0-dev
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: "2023-03-27T20:45:08Z"
labels:
app: dep-ctr-pod
name: dep-ctr-pod-deployment
spec:
replicas: 3
selector:
matchLabels:
app: dep-ctr-pod
template:
metadata:
annotations:
io.podman.annotations.ulimit: nofile=524288:524288,nproc=127332:127332
creationTimestamp: "2023-03-27T20:45:08Z"
labels:
app: dep-ctr-pod
name: dep-ctr-pod
spec:
containers:
- command:
- top
image: docker.io/library/alpine:latest
name: dep-ctr
```


Create Kubernetes Pod YAML for a container with the directory `/home/user/my-data` on the host bind-mounted in the container to `/volume`.
```
$ podman kube generate my-container-with-bind-mounted-data
Expand Down
8 changes: 8 additions & 0 deletions libpod/define/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,11 @@ const (
// ContainerInitPath is the default path of the mounted container init.
ContainerInitPath = "/run/podman-init"
)

// Kubernetes Kinds
const (
// A Pod kube yaml spec
K8sKindPod = "pod"
// A Deployment kube yaml spec
K8sKindDeployment = "deployment"
)
95 changes: 93 additions & 2 deletions libpod/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
cutil "github.com/containers/common/pkg/util"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/annotations"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/env"
v1 "github.com/containers/podman/v4/pkg/k8s.io/api/core/v1"
"github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/api/resource"
Expand Down Expand Up @@ -107,8 +108,8 @@ func (p *Pod) GenerateForKube(ctx context.Context, getService bool) (*v1.Pod, []
pod.Spec.RestartPolicy = v1.RestartPolicyOnFailure
case define.RestartPolicyNo:
pod.Spec.RestartPolicy = v1.RestartPolicyNever
default: // some pod create from cmdline, such as "", so set it to Never
pod.Spec.RestartPolicy = v1.RestartPolicyNever
default: // some pod create from cmdline, such as "", so set it to "" as k8s automatically defaults to always
pod.Spec.RestartPolicy = ""
vrothberg marked this conversation as resolved.
Show resolved Hide resolved
}
break
}
Expand All @@ -131,6 +132,65 @@ func (p *Pod) getInfraContainer() (*Container, error) {
return p.runtime.GetContainer(infraID)
}

// GenerateForKubeDeployment returns a YAMLDeployment from a YAMLPod that is then used to create a kubernetes Deployment
// kind YAML.
func GenerateForKubeDeployment(ctx context.Context, pod *YAMLPod, options entities.GenerateKubeOptions) (*YAMLDeployment, error) {
// Restart policy for Deployments can only be set to Always
if options.Type == define.K8sKindDeployment && !(pod.Spec.RestartPolicy == "" || pod.Spec.RestartPolicy == define.RestartPolicyAlways) {
return nil, fmt.Errorf("k8s Deployments can only have restartPolicy set to Always")
}

// Create label map that will be added to podSpec and Deployment metadata
// The matching label lets the deployment know which pods to manage
appKey := "app"
matchLabels := map[string]string{appKey: pod.Name}
// Add the key:value (app:pod-name) to the podSpec labels
if pod.Labels == nil {
pod.Labels = matchLabels
} else {
pod.Labels[appKey] = pod.Name
}

depSpec := YAMLDeploymentSpec{
DeploymentSpec: v1.DeploymentSpec{
Selector: &v12.LabelSelector{
MatchLabels: matchLabels,
},
},
Template: &YAMLPodTemplateSpec{
PodTemplateSpec: v1.PodTemplateSpec{
ObjectMeta: pod.ObjectMeta,
},
Spec: pod.Spec,
},
}

// Add replicas count if user adds replica number with --replicas flag and is greater than 1
// If replicas is set to 1, no need to add it to the generated yaml as k8s automatically defaults
// to that. Podman as sets replicas to 1 by default.
if options.Replicas > 1 {
depSpec.Replicas = &options.Replicas
}

// Create the Deployment opbject
dep := YAMLDeployment{
Deployment: v1.Deployment{
ObjectMeta: v12.ObjectMeta{
Name: pod.Name + "-deployment",
CreationTimestamp: pod.CreationTimestamp,
Labels: pod.Labels,
},
TypeMeta: v12.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
},
Spec: &depSpec,
}

return &dep, nil
}

// GenerateForKube generates a v1.PersistentVolumeClaim from a libpod volume.
func (v *Volume) GenerateForKube() *v1.PersistentVolumeClaim {
annotations := make(map[string]string)
Expand Down Expand Up @@ -195,6 +255,37 @@ type YAMLPod struct {
Status *v1.PodStatus `json:"status,omitempty"`
}

// YAMLPodTemplateSpec represents the same k8s API core PodTemplateStruct with a
// small change and that is having Spec as a pointer to YAMLPodSpec.
// Because Go doesn't omit empty struct and we want to omit any empty structs in the
// Pod yaml. This is used when generating a Deployment kind.
type YAMLPodTemplateSpec struct {
v1.PodTemplateSpec
Spec *YAMLPodSpec `json:"spec,omitempty"`
}

// YAMLDeploymentSpec represents the same k8s API core DeploymentSpec with a small
// change and that is having Template as a pointer to YAMLPodTemplateSpec and Strategy
// as a pointer to k8s API core DeploymentStrategy.
// Because Go doesn't omit empty struct and we want to omit Strategy and any fields in the Pod YAML
// if it's empty.
type YAMLDeploymentSpec struct {
v1.DeploymentSpec
Template *YAMLPodTemplateSpec `json:"template,omitempty"`
Strategy *v1.DeploymentStrategy `json:"strategy,omitempty"`
}

// YAMLDeployment represents the same k8s API core Deployment with a small change
// and that is having Spec as a pointer to YAMLDeploymentSpec and Status as a pointer to
// k8s API core DeploymentStatus.
// Because Go doesn't omit empty struct and we want to omit Status and any fields in the DeploymentSpec
// if it's empty.
type YAMLDeployment struct {
v1.Deployment
Spec *YAMLDeploymentSpec `json:"spec,omitempty"`
Status *v1.DeploymentStatus `json:"status,omitempty"`
}

// YAMLService represents the same k8s API core Service struct with a small
// change and that is having Status as a pointer to k8s API core ServiceStatus.
// Because Go doesn't omit empty struct and we want to omit Status in YAML
Expand Down
11 changes: 8 additions & 3 deletions pkg/api/handlers/libpod/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"

"github.com/containers/podman/v4/libpod"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/api/handlers/utils"
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/containers/podman/v4/pkg/domain/entities"
Expand Down Expand Up @@ -89,10 +90,14 @@ func GenerateKube(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
Names []string `schema:"names"`
Service bool `schema:"service"`
Names []string `schema:"names"`
Service bool `schema:"service"`
Type string `schema:"type"`
Replicas int32 `schema:"replicas"`
}{
// Defaults would go here.
Type: define.K8sKindPod,
Replicas: 1,
}

if err := decoder.Decode(&query, r.URL.Query()); err != nil {
Expand All @@ -101,7 +106,7 @@ func GenerateKube(w http.ResponseWriter, r *http.Request) {
}

containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.GenerateKubeOptions{Service: query.Service}
options := entities.GenerateKubeOptions{Service: query.Service, Type: query.Type, Replicas: query.Replicas}
report, err := containerEngine.GenerateKube(r.Context(), query.Names, options)
if err != nil {
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("generating YAML: %w", err))
Expand Down
11 changes: 11 additions & 0 deletions pkg/api/server/register_kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,17 @@ func (s *APIServer) registerKubeHandlers(r *mux.Router) error {
// type: boolean
// default: false
// description: Generate YAML for a Kubernetes service object.
// - in: query
// name: type
// type: string
// default: pod
// description: Generate YAML for the given Kubernetes kind.
// - in: query
// name: replicas
// type: integer
// format: int32
// default: 0
// description: Set the replica number for Deployment kind.
// produces:
// - text/vnd.yaml
// - application/json
Expand Down
4 changes: 4 additions & 0 deletions pkg/bindings/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"net/http"
"strconv"

"github.com/containers/podman/v4/pkg/bindings"
"github.com/containers/podman/v4/pkg/domain/entities"
Expand Down Expand Up @@ -54,6 +55,9 @@ func Kube(ctx context.Context, nameOrIDs []string, options *KubeOptions) (*entit
for _, name := range nameOrIDs {
params.Add("names", name)
}
if options.Replicas != nil {
params.Set("replicas", strconv.Itoa(int(*options.Replicas)))
}
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/generate/kube", params, nil)
if err != nil {
return nil, err
Expand Down
4 changes: 4 additions & 0 deletions pkg/bindings/generate/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ package generate
type KubeOptions struct {
// Service - generate YAML for a Kubernetes _service_ object.
Service *bool
// Type - the k8s kind to be generated i.e Pod or Deployment
Type *string
// Replicas - the value to set in the replicas field for a Deployment
Replicas *int32
}

// SystemdOptions are optional options for generating systemd files
Expand Down
30 changes: 30 additions & 0 deletions pkg/bindings/generate/types_kube_options.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pkg/domain/entities/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ type GenerateSystemdReport struct {
type GenerateKubeOptions struct {
// Service - generate YAML for a Kubernetes _service_ object.
Service bool
// Type - the k8s kind to be generated i.e Pod or Deployment
Type string
// Replicas - the value to set in the replicas field for a Deployment
Replicas int32
}

type KubeGenerateOptions = GenerateKubeOptions
Expand Down
Loading