Skip to content

Commit

Permalink
Generate Kubernetes PersistentVolumeClaims from named volumes
Browse files Browse the repository at this point in the history
Fixes containers#5788

This commit adds support for named volumes in podman-generate-kube.
Named volumes are output in the YAML as PersistentVolumeClaims.
To avoid naming conflicts, the volume name is suffixed with "-pvc".
This commit adds a corresponding suffix for host path mounts.
Host path volumes are suffixed with "-host".

Signed-off-by: Jordan Williams <[email protected]>
  • Loading branch information
jwillikers authored and mheon committed Mar 29, 2021
1 parent c9640ba commit 633ae01
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 7 deletions.
115 changes: 113 additions & 2 deletions docs/source/markdown/podman-generate-kube.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ podman-generate-kube - Generate Kubernetes YAML based on a pod or container
**podman generate kube** [*options*] *container...* | *pod*

## DESCRIPTION
**podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from Podman one or more containers or a single pod. Whether
**podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from Podman from one or more containers or a single pod. Whether
the input is for containers or a pod, Podman will always generate the specification as a Pod. The input may be in the form
of a pod or one or more container names or IDs.

Volumes appear in the generated YAML according to two different volume types. Bind-mounted volumes become *hostPath* volume types and named volumes become *persistentVolumeClaim* volume types. Generated *hostPath* volume types will be one of three subtypes depending on the state of the host path: *DirectoryOrCreate* when no file or directory exists at the host, *Directory* when host path is a directory, or *File* when host path is a file. The value for *claimName* for a *persistentVolumeClaim* is the name of the named volume registered in Podman.

Potential name conflicts between volumes are avoided by using a standard naming scheme for each volume type. The *hostPath* volume types are named according to the path on the host machine, replacing forward slashes with hyphens less any leading and trailing forward slashes. The special case of the filesystem root, `/`, translates to the name `root`. Additionally, the name is suffixed with `-host` to avoid naming conflicts with *persistentVolumeClaim* volumes. Each *persistentVolumeClaim* volume type uses the name of its associated named volume suffixed with `-pvc`.

Note that the generated Kubernetes YAML file can be used to re-run the deployment via podman-play-kube(1).

## OPTIONS
Expand All @@ -25,7 +29,7 @@ random port is assigned by Podman in the specification.

## EXAMPLES

Create Kubernetes Pod YAML for a container called `some-mariadb` .
Create Kubernetes Pod YAML for a container called `some-mariadb`.
```
$ sudo podman generate kube some-mariadb
# Generation of Kubernetes YAML is still under development!
Expand Down Expand Up @@ -81,6 +85,113 @@ spec:
status: {}
```

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 generate kube my-container-with-bind-mounted-data
# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-3.1.0-dev
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2021-03-18T16:26:08Z"
labels:
app: my-container-with-bind-mounted-data
name: my-container-with-bind-mounted-data
spec:
containers:
- command:
- /bin/sh
env:
- name: PATH
value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
- name: TERM
value: xterm
- name: container
value: podman
image: docker.io/library/alpine:latest
name: test-bind-mount
resources: {}
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- CAP_MKNOD
- CAP_NET_RAW
- CAP_AUDIT_WRITE
privileged: false
readOnlyRootFilesystem: false
seLinuxOptions: {}
volumeMounts:
- mountPath: /volume
name: home-user-my-data-host
workingDir: /
dnsConfig: {}
restartPolicy: Never
volumes:
- hostPath:
path: /home/user/my-data
type: Directory
name: home-user-my-data-host
status: {}
```

Create Kubernetes Pod YAML for a container with the named volume `priceless-data` mounted in the container at `/volume`.
```
$ podman generate kube my-container-using-priceless-data
# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-3.1.0-dev
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2021-03-18T16:26:08Z"
labels:
app: my-container-using-priceless-data
name: my-container-using-priceless-data
spec:
containers:
- command:
- /bin/sh
env:
- name: PATH
value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
- name: TERM
value: xterm
- name: container
value: podman
image: docker.io/library/alpine:latest
name: test-bind-mount
resources: {}
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- CAP_MKNOD
- CAP_NET_RAW
- CAP_AUDIT_WRITE
privileged: false
readOnlyRootFilesystem: false
seLinuxOptions: {}
volumeMounts:
- mountPath: /volume
name: priceless-data-pvc
workingDir: /
dnsConfig: {}
restartPolicy: Never
volumes:
- name: priceless-data-pvc
persistentVolumeClaim:
claimName: priceless-data
status: {}
```

Create Kubernetes Pod YAML for a pod called `demoweb` and include a service.
```
$ sudo podman generate kube -s demoweb
Expand Down
4 changes: 3 additions & 1 deletion docs/source/markdown/podman-play-kube.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ podman-play-kube - Create pods and containers based on Kubernetes YAML

Ideally the input file would be one created by Podman (see podman-generate-kube(1)). This would guarantee a smooth import and expected results.

Note: HostPath volume types created by play kube will be given an SELinux private label (Z)
Only two volume types are supported by play kube, the *hostPath* and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, and *Socket* subtypes are supported. The *CharDevice* and *BlockDevice* subtypes are not supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume.

Note: *hostPath* volume types created by play kube will be given an SELinux private label (Z)

Note: If the `:latest` tag is used, Podman will attempt to pull the image from a registry. If the image was built locally with Podman or Buildah, it will have `localhost` as the domain, in that case, Podman will use the image from the local store even if it has the `:latest` tag.

Expand Down
32 changes: 28 additions & 4 deletions libpod/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,6 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, *v1.PodDNS
}

if len(c.config.UserVolumes) > 0 {
// TODO When we until we can resolve what the volume name should be, this is disabled
// Volume names need to be coordinated "globally" in the kube files.
volumeMounts, volumes, err := libpodMountsToKubeVolumeMounts(c)
if err != nil {
return kubeContainer, kubeVolumes, nil, err
Expand Down Expand Up @@ -493,8 +491,7 @@ func libpodEnvVarsToKubeEnvVars(envs []string) ([]v1.EnvVar, error) {

// libpodMountsToKubeVolumeMounts converts the containers mounts to a struct kube understands
func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, []v1.Volume, error) {
// TODO when named volumes are supported in play kube, also parse named volumes here
_, mounts := c.sortUserVolumes(c.config.Spec)
namedVolumes, mounts := c.sortUserVolumes(c.config.Spec)
vms := make([]v1.VolumeMount, 0, len(mounts))
vos := make([]v1.Volume, 0, len(mounts))
for _, m := range mounts {
Expand All @@ -505,9 +502,34 @@ func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, []v1.Volume
vms = append(vms, vm)
vos = append(vos, vo)
}
for _, v := range namedVolumes {
vm, vo := generateKubePersistentVolumeClaim(v)
vms = append(vms, vm)
vos = append(vos, vo)
}
return vms, vos, nil
}

// generateKubePersistentVolumeClaim converts a ContainerNamedVolume to a Kubernetes PersistentVolumeClaim
func generateKubePersistentVolumeClaim(v *ContainerNamedVolume) (v1.VolumeMount, v1.Volume) {
ro := util.StringInSlice("ro", v.Options)

// To avoid naming conflicts with any host path mounts, add a unique suffix to the volume's name.
name := v.Name + "-pvc"

vm := v1.VolumeMount{}
vm.Name = name
vm.MountPath = v.Dest
vm.ReadOnly = ro

pvc := v1.PersistentVolumeClaimVolumeSource{ClaimName: v.Name, ReadOnly: ro}
vs := v1.VolumeSource{}
vs.PersistentVolumeClaim = &pvc
vo := v1.Volume{Name: name, VolumeSource: vs}

return vm, vo
}

// generateKubeVolumeMount takes a user specified mount and returns
// a kubernetes VolumeMount (to be added to the container) and a kubernetes Volume
// (to be added to the pod)
Expand All @@ -519,6 +541,8 @@ func generateKubeVolumeMount(m specs.Mount) (v1.VolumeMount, v1.Volume, error) {
if err != nil {
return vm, vo, err
}
// To avoid naming conflicts with any persistent volume mounts, add a unique suffix to the volume's name.
name += "-host"
vm.Name = name
vm.MountPath = m.Destination
if util.StringInSlice("ro", m.Options) {
Expand Down
30 changes: 30 additions & 0 deletions test/e2e/generate_kube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,36 @@ var _ = Describe("Podman generate kube", func() {
Expect(inspect.OutputToString()).To(ContainSubstring(vol1))
})

It("podman generate kube with persistent volume claim", func() {
vol := "vol-test-persistent-volume-claim"

// we need a container name because IDs don't persist after rm/play
ctrName := "test-persistent-volume-claim"
ctrNameInKubePod := "test1-test-persistent-volume-claim"

session := podmanTest.Podman([]string{"run", "-d", "--pod", "new:test1", "--name", ctrName, "-v", vol + ":/volume/:z", "alpine", "top"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))

outputFile := filepath.Join(podmanTest.RunRoot, "pod.yaml")
kube := podmanTest.Podman([]string{"generate", "kube", "test1", "-f", outputFile})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))

rm := podmanTest.Podman([]string{"pod", "rm", "-f", "test1"})
rm.WaitWithDefaultTimeout()
Expect(rm.ExitCode()).To(Equal(0))

play := podmanTest.Podman([]string{"play", "kube", outputFile})
play.WaitWithDefaultTimeout()
Expect(play.ExitCode()).To(Equal(0))

inspect := podmanTest.Podman([]string{"inspect", ctrNameInKubePod})
inspect.WaitWithDefaultTimeout()
Expect(inspect.ExitCode()).To(Equal(0))
Expect(inspect.OutputToString()).To(ContainSubstring(vol))
})

It("podman generate kube sharing pid namespace", func() {
podName := "test"
podSession := podmanTest.Podman([]string{"pod", "create", "--name", podName, "--share", "pid"})
Expand Down

0 comments on commit 633ae01

Please sign in to comment.