diff options
-rw-r--r-- | cmd/podman/containers/cp.go | 4 | ||||
-rw-r--r-- | docs/source/Tutorials.rst | 1 | ||||
-rw-r--r-- | docs/tutorials/README.md | 4 | ||||
-rw-r--r-- | docs/tutorials/basic_networking.md | 291 | ||||
-rw-r--r-- | docs/tutorials/podman_bridge.png | bin | 0 -> 48144 bytes | |||
-rw-r--r-- | docs/tutorials/podman_macvlan.png | bin | 0 -> 37646 bytes | |||
-rw-r--r-- | docs/tutorials/podman_pod.png | bin | 0 -> 53274 bytes | |||
-rw-r--r-- | docs/tutorials/podman_rootless_default.png | bin | 0 -> 40293 bytes | |||
-rw-r--r-- | pkg/domain/infra/abi/archive.go | 7 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.c | 6 | ||||
-rw-r--r-- | test/system/065-cp.bats | 26 |
11 files changed, 333 insertions, 6 deletions
diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go index 69b61a06c..5db05719c 100644 --- a/cmd/podman/containers/cp.go +++ b/cmd/podman/containers/cp.go @@ -311,8 +311,8 @@ func copyToContainer(container string, containerPath string, hostPath string) er } getOptions := buildahCopiah.GetOptions{ - // Unless the specified path ends with ".", we want to copy the base directory. - KeepDirectoryNames: !strings.HasSuffix(hostPath, "."), + // Unless the specified points to ".", we want to copy the base directory. + KeepDirectoryNames: hostInfo.IsDir && filepath.Base(hostPath) != ".", } if !hostInfo.IsDir && (!containerInfo.IsDir || containerInfoErr != nil) { // If we're having a file-to-file copy, make sure to diff --git a/docs/source/Tutorials.rst b/docs/source/Tutorials.rst index e3e869d5b..e48d1e853 100644 --- a/docs/source/Tutorials.rst +++ b/docs/source/Tutorials.rst @@ -11,3 +11,4 @@ Here are a number of useful tutorials to get you up and running with Podman. If * `Podman remote-client tutorial <https://github.com/containers/podman/blob/master/docs/tutorials/remote_client.md>`_: A brief how-to on using the Podman remote-client. * `How to use libpod for custom/derivative projects <https://github.com/containers/podman/blob/master/docs/tutorials/podman-derivative-api.md>`_: How the libpod API can be used within your own project. * `How to use Podman's Go bindings <https://github.com/containers/podman/blob/master/docs/tutorials/podman-go-bindings.md>`_: A brief how-to on using Podman's Go bindings in external applications. +* `Common network setups <https://github.com/containers/podman/blob/master/docs/tutorials/basic_networking.md>`_: A basic guide to common network setups for Podman. diff --git a/docs/tutorials/README.md b/docs/tutorials/README.md index 246d235ee..455459062 100644 --- a/docs/tutorials/README.md +++ b/docs/tutorials/README.md @@ -31,3 +31,7 @@ Learn how to setup and use image signing with Podman. **[Go Bindings](podman-go-bindings.md)** A brief how-to on using Podman's Go bindings in external applications. + +**[Go Bindings](basic_networking.md)** + +A basic guide to common network setups with Podman diff --git a/docs/tutorials/basic_networking.md b/docs/tutorials/basic_networking.md new file mode 100644 index 000000000..7544c1cfd --- /dev/null +++ b/docs/tutorials/basic_networking.md @@ -0,0 +1,291 @@ +![PODMAN logo](../../logo/podman-logo-source.svg) + + +# Basic Networking Guide for Podman + + +It seems once people master the basics of containers, networking is one of the first +aspects they begin experimenting with. And in regards to networking, it takes very +little experimentation before ending up on the deep end of the pool. The following +guide shows the most common network setups for Podman rootfull and rootless containers. +Each setup is supported with an example. + + +## Differences between rootfull and rootless container networking + +One of the guiding factors on networking for containers with Podman is going to be +whether or not the container is run by a root user or not. This is because unprivileged +users cannot create networking interfaces on the host. Therefore, with rootfull +containers, the default networking mode is to use the Container Network Interface +(CNI) plugins and specifically the bridge plugin. For rootless, the default network +mode is slirp4netns. Because of the limited privileges, slirp4netns lacks some of +the features of CNI networking; for example, slirp4netns cannot give containers a +routable IP address. + +## Firewalls + +The role of a firewall will not impact the setup and configuration of networking, +but it will impact traffic on those networks. The most obvious is inbound network +traffic to the container host, which is being passed onto containers usually with +port mapping. Depending on the firewall implementation, we have observed firewall +ports being opened automatically due to running a container with a port mapping (for +example). If container traffic does not seem to work properly, check the firewall +and allow traffic on ports the container is using. A common problem is that +reloading the firewall deletes the cni iptables rules resulting in a loss of +network connectivity for rootful containers. Podman v3 provides the podman +network reload command to restore this without having to restart the container. + +## Basic Network Setups + +Most containers and pods being run with Podman adhere to a couple of simple scenarios. +By default, rootfull Podman will create a bridged network. This is the most straightforward +and preferred network setup for Podman. Bridge networking creates an interface for +the container on an internal bridge network, which is then connected to the internet +via Network Address Translation(NAT). We also see users wanting to use `macvlan` +for networking as well. The `macvlan` plugin forwards an entire network interface +from the host into the container, allowing it access to the network the host is connected +to. And finally, the default network configuration for rootless containers is slirp4netns. +The slirp4netns network mode has limited capabilities but can be run on users without +root privileges. It creates a tunnel from the host into the container to forward +traffic. + +### Bridge + +CNI defines a bridge network as where an internal network is created where both the +container and host are attached. Then this network is capable of allowing the containers +to communicate outside of the host. + + +![bridge_network](podman_bridge.png) + +Consider the above illustration. It depicts a laptop user running two containers: +a web and db instance. These two containers are on the virtual network with the +host. Additionally, by default, these containers can initiate communications outside +the laptop (to the Internet for example). The containers on the virtual network +typically have non-routable, also known as private IP addresses. + +When dealing with communication that is being initiated outside the host, the outside +client typically must address the laptop’s external network interface and given port +number. Assuming the host allows incoming traffic, the host will know to forward +the incoming traffic on that port to the specific container. To accomplish this, +firewall rules are added to forward traffic when a container requests a specific +port be forwarded. + +Bridge networking is the default for Podman containers created as root. Podman provides +a default bridge network, but you can create others using the `podman network create` +command. Containers can be joined to a CNI network when they are created with the +`--network` flag, or after they are created via the `podman network connect` and +`podman network disconnect` commands. + +As mentioned earlier, slirp4netns is the default network configuration for rootless +users. But as of Podman version 3.0, rootless users can also use CNI networking. +The user experience of rootless CNI is very akin to a rootfull CNI, except that +there is no default network configuration provided. You simply need to create a +network, and the one will be created as a bridge network. + +``` +$ podman network create +``` + +When rootless containers are run with a CNI networking configuration, a “side-car” +container for running CNI is also run. Do not remove this container while your rootless +containers are running. if you remove this container (e.g by accident) all attached +containers lose network connectivity. In order to restore the network connectivity +all containers with networks must be restarted. This will automatically recreate +the "side-car" container. For rootfull containers, there is no “side-car” container +as rootfull users have the permissions to create and modify network interfaces on +the host. + +#### Example + +By default, rootfull containers use the CNI bridge plugin for its default configuration. +In this case, no network name must be passed to Podman. However, you can create +additional bridged networks with the podman create command. In that case, you will +have to set the network name. + +The following example shows how to set up a web server and expose it to the network +outside the host as both rootfull and rootless. It will also show how an outside +client can connect to the container. + +``` +(rootfull) $ sudo podman run -dt --name webserver -p 8080:80 quay.io/libpod/banner +00f3440c7576aae2d5b193c40513c29c7964e96bf797cf0cc352c2b68ccbe66a +``` + +As mentioned earlier, for rootless containers using CNI, a network must first be +created. +``` +$ podman network create +/home/baude/.config/cni/net.d/cni-podman1.conflist +``` +Now run the container. +``` +$ podman run -dt --name webserver --net cni-podman1 -p 8081:80 quay.io/libpod/banner +269fd0d6b2c8ed60f2ca41d7beceec2471d72fb9a33aa8ca45b81dc9a0abbb12 +``` +Note in the above run command, the container’s port 80 (where the Nginx server is +running) was mapped to the host’s port 8080. Port 8080 was chosen to demonstrate +how the host and container ports can be mapped for external access. The port could +very well have been 80 as well (except for rootless users). + +To connect from an outside client to the webserver, simply point an HTTP client to +the host’s IP address at port 8080 for rootfull and port 8081 for rootless. +``` +(outside_host): $ curl 192.168.99.109:8080 + ___ __ + / _ \___ ___/ /_ _ ___ ____ + / ___/ _ \/ _ / ' \/ _ `/ _ \ +/_/ \___/\_,_/_/_/_/\_,_/_//_/ + +(outside_host): $ curl 192.168.99.109:8081 + ___ __ + / _ \___ ___/ /_ _ ___ ____ + / ___/ _ \/ _ / ' \/ _ `/ _ \ +/_/ \___/\_,_/_/_/_/\_,_/_//_/ +``` + +### Macvlan + +With macvlan, the container is given access to a physical network interface on the +host. This interface can configure multiple subinterfaces. And each subinterface +is capable of having its own MAC and IP address. In the case of Podman containers, +the container will present itself as if it is on the same network as the host. + +![macvlan_network](podman_bridge.png) + +In the illustration, outside clients will be able to access the web container by +its IP address directly. Usually the network information, including IP address, +is leased from a DHCP server like most other network clients on the network. If +the laptop is running a firewall, such as firewalld, then accommodations will need +to be made for proper access. + +#### Example + +The following example demonstrates how to set up a web container on a macvlan and +how to access that container from outside the host. First, create the macvlan network. + You need to know the network interface on the host that connects to the routable +network. In the example case, it is eth0. +``` +$ sudo podman network create -d macvlan -o parent=eth0 webnetwork +/etc/cni/net.d/webnetwork.conflist +``` +The next step is to ensure that the DHCP CNI plugin is running. This plugin facilitates +the DHCP lease from the network. +``` +$ sudo /usr/libexec/cni/dhcp daemon +``` +Now run the container and be certain to attach it to the network we created earlier. +``` +$ sudo podman run -dt --name webserver --network webnetwork quay.io/libpod/banner +03d82083c434d7e937fc0b87c25401f46ab5050007df403bf988e25e52c5cc40 +[baude@localhost ~]$ sudo podman exec webserver ip address show eth0 +2: eth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state +UP +link/ether 0a:3c:e2:eb:87:0f brd ff:ff:ff:ff:ff:ff +inet 192.168.99.186/24 brd 192.168.99.255 scope global eth0 +valid_lft forever preferred_lft forever +inet6 fe80::83c:e2ff:feeb:870f/64 scope link +valid_lft forever preferred_lft forever +``` +Because the container has a routable IP address (on this network) and is not being +managed by firewalld, no change to the firewall is needed. +``` +(outside_host): $ curl http://192.168.99.186 + ___ __ + / _ \___ ___/ /_ _ ___ ____ + / ___/ _ \/ _ / ' \/ _ `/ _ \ +/_/ \___/\_,_/_/_/_/\_,_/_//_/ +``` + + + +### Slirp4netns + +Slirp4netns is the default network setup for rootless containers and pods. It was +invented because unprivileged users are not allowed to make network interfaces on +the host. Slirp4netns creates a TAP device in the container’s network namespace +and connects to the usermode TCP/IP stack. Consider the following illustration. + +![slirp_network](podman_rootless_default.png) + +The unprivileged user on this laptop has created two containers: a DB container and +a web container. Both of these containers have the ability to access content on +networks outside the laptop. And outside clients can access the containers if the +container is bound to a host port and the laptop firewall allows it. Remember, unprivileged +users must use ports 1024 through 65535 as lower ports require root privileges. (CAP_NET_BIND_SERVICE) +Note: this can be adjusted using the `sysctl net.ipv4.ip_unprivileged_port_start` + +One of the drawbacks of slirp4netns is that the containers are completely isolated +from each other. Unlike the bridge approach, there is no virtual network. For containers +to communicate with each other, they can use the port mappings with the host system, +or they can be put into a Pod where they share the same network namespace. See [Communicating +between containers and pods](#Communicating-between-containers-and-pods) for more information. + +#### Example + +The following example will show how two rootless containers can communicate with +each other where one is a web server. Then it will show how a client on the host’s +network can communicate with the rootless web server. + +First, run the rootless web server and map port 80 from the container to a non-privileged +port like 8080. +``` +$ podman run -dt --name webserver -p 8080:80 quay.io/libpod/banner +17ea33ccd7f55ff45766b3ec596b990a5f2ba66eb9159cb89748a85dc3cebfe0 +``` +Because rootfull containers cannot communicate with each other directly with TCP/IP +via IP addresses, the host and the port mapping are used. To do so, the IP address +of the host (interface) must be known. +``` +$ ip address show eth0 +3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group +default qlen 1000 +link/ether 3c:e1:a1:c1:7a:3f brd ff:ff:ff:ff:ff:ff +altname eth0 +inet 192.168.99.109/24 brd 192.168.99.255 scope global dynamic noprefixroute eth0 +valid_lft 78808sec preferred_lft 78808sec +inet6 fe80::5632:6f10:9e76:c33/64 scope link noprefixroute +valid_lft forever preferred_lft forever +``` +From another rootless container, use the host’s IP address and port to communicate +between the two rootless containers successfully. +``` +$ podman run -it quay.io/libpod/banner curl http://192.168.99.109:8080 + ___ __ + / _ \___ ___/ /_ _ ___ ____ + / ___/ _ \/ _ / ' \/ _ `/ _ \ +/_/ \___/\_,_/_/_/_/\_,_/_//_/ +``` + +From a client outside the host, the IP address and port can also be used: +``` +(outside_host): $ curl http://192.168.99.109:8080 + ___ __ + / _ \___ ___/ /_ _ ___ ____ + / ___/ _ \/ _ / ' \/ _ `/ _ \ +/_/ \___/\_,_/_/_/_/\_,_/_//_/ +``` + +## Communicating between containers and pods + +Most users of containers have a decent understanding of how containers communicate +with each other and the rest of the world. Usually each container has its own IP +address and networking information. They communicate amongst each other using regular +TCP/IP means like IP addresses or, in many cases, using DNS names often based on +the container name. But pods are a collection of one or more containers, and with +that, some uniqueness is inherited. + +By definition, all containers in a Podman pod share the same network namespace. This +fact means that they will have the same IP address, MAC addresses, and port mappings. +You can conveniently communicate between containers in a pod by using localhost. + +![slirp_network](podman_pod.png) + +The above illustration describes a Pod on a bridged network. As depicted, the Pod +has two containers “inside” it: a DB and a Web container. Because they share the +same network namespace, the DB and Web container can communicate with each other +using localhost (127.0.0.1). Furthermore, they are also both addressable by the +IP address (and DNS name if applicable) assigned to the Pod itself. + +For more information on container to container networking, see Configuring container +networking with Podman. diff --git a/docs/tutorials/podman_bridge.png b/docs/tutorials/podman_bridge.png Binary files differnew file mode 100644 index 000000000..c0cb88951 --- /dev/null +++ b/docs/tutorials/podman_bridge.png diff --git a/docs/tutorials/podman_macvlan.png b/docs/tutorials/podman_macvlan.png Binary files differnew file mode 100644 index 000000000..04d9c9080 --- /dev/null +++ b/docs/tutorials/podman_macvlan.png diff --git a/docs/tutorials/podman_pod.png b/docs/tutorials/podman_pod.png Binary files differnew file mode 100644 index 000000000..17718e20b --- /dev/null +++ b/docs/tutorials/podman_pod.png diff --git a/docs/tutorials/podman_rootless_default.png b/docs/tutorials/podman_rootless_default.png Binary files differnew file mode 100644 index 000000000..5d4a66644 --- /dev/null +++ b/docs/tutorials/podman_rootless_default.png diff --git a/pkg/domain/infra/abi/archive.go b/pkg/domain/infra/abi/archive.go index c64dfb02a..f38f5c132 100644 --- a/pkg/domain/infra/abi/archive.go +++ b/pkg/domain/infra/abi/archive.go @@ -3,6 +3,7 @@ package abi import ( "context" "io" + "path/filepath" "strings" buildahCopiah "github.com/containers/buildah/copier" @@ -93,7 +94,7 @@ func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID containerPath = "/." } - _, resolvedRoot, resolvedContainerPath, err := ic.containerStat(container, containerMountPoint, containerPath) + statInfo, resolvedRoot, resolvedContainerPath, err := ic.containerStat(container, containerMountPoint, containerPath) if err != nil { unmount() return nil, err @@ -110,8 +111,8 @@ func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID return func() error { defer container.Unmount(false) getOptions := buildahCopiah.GetOptions{ - // Unless the specified path ends with ".", we want to copy the base directory. - KeepDirectoryNames: !strings.HasSuffix(resolvedContainerPath, "."), + // Unless the specified points to ".", we want to copy the base directory. + KeepDirectoryNames: statInfo.IsDir && filepath.Base(containerPath) != ".", UIDMap: idMappings.UIDMap, GIDMap: idMappings.GIDMap, ChownDirs: idPair, diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 2e1fddc48..d588d848b 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -196,7 +196,11 @@ can_use_shortcut () return false; if (strstr (argv[0], "podman") == NULL) - return false; + { + free (argv[0]); + free (argv); + return false; + } for (argc = 0; argv[argc]; argc++) { diff --git a/test/system/065-cp.bats b/test/system/065-cp.bats index d3cf1c274..0fcc437d4 100644 --- a/test/system/065-cp.bats +++ b/test/system/065-cp.bats @@ -18,6 +18,8 @@ load helpers echo "${randomcontent[0]}" > $srcdir/hostfile0 echo "${randomcontent[1]}" > $srcdir/hostfile1 echo "${randomcontent[2]}" > $srcdir/hostfile2 + mkdir -p $srcdir/subdir + echo "${randomcontent[2]}" > $srcdir/subdir/dotfile. run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity run_podman exec cpcontainer mkdir /srv/subdir @@ -50,6 +52,11 @@ load helpers is "$output" "${randomcontent[$id]}" "$description (cp -> ctr:$dest)" done < <(parse_table "$tests") + # Dots are special for dirs not files. + run_podman cp $srcdir/subdir/dotfile. cpcontainer:/tmp + run_podman exec cpcontainer cat /tmp/dotfile. + is "$output" "${randomcontent[2]}" "$description (cp -> ctr:$dest)" + # Host path does not exist. run_podman 125 cp $srcdir/IdoNotExist cpcontainer:/tmp is "$output" 'Error: ".*/IdoNotExist" could not be found on the host' \ @@ -76,12 +83,14 @@ load helpers ) run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity run_podman exec cpcontainer sh -c "echo ${randomcontent[0]} > /tmp/containerfile" + run_podman exec cpcontainer sh -c "echo ${randomcontent[0]} > /tmp/dotfile." run_podman exec cpcontainer sh -c "echo ${randomcontent[1]} > /srv/containerfile1" run_podman exec cpcontainer sh -c "mkdir /srv/subdir; echo ${randomcontent[2]} > /srv/subdir/containerfile2" # format is: <id> | <source arg to cp> | <destination arg (appended to $srcdir) to cp> | <full dest path (appended to $srcdir)> | <test name> tests=" 0 | /tmp/containerfile | | /containerfile | copy to srcdir/ +0 | /tmp/dotfile. | | /dotfile. | copy to srcdir/ 0 | /tmp/containerfile | / | /containerfile | copy to srcdir/ 0 | /tmp/containerfile | /. | /containerfile | copy to srcdir/. 0 | /tmp/containerfile | /newfile | /newfile | copy to srcdir/newfile @@ -117,12 +126,18 @@ load helpers echo "${randomcontent[0]}" > $srcdir/hostfile0 echo "${randomcontent[1]}" > $srcdir/hostfile1 + # "." and "dir/." will copy the contents, so make sure that a dir ending + # with dot is treated correctly. + mkdir -p $srcdir. + cp $srcdir/* $srcdir./ + run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity run_podman exec cpcontainer mkdir /srv/subdir # format is: <source arg to cp (appended to srcdir)> | <destination arg to cp> | <full dest path> | <test name> tests=" | / | /dir-test | copy to root + . | / | /dir-test. | copy dotdir to root / | /tmp | /tmp/dir-test | copy to tmp /. | /usr/ | /usr/ | copy contents of dir to usr/ | . | /srv/dir-test | copy to workdir (rel path) @@ -153,6 +168,9 @@ load helpers run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity run_podman exec cpcontainer sh -c 'mkdir /srv/subdir; echo "This first file is on the container" > /srv/subdir/containerfile1' run_podman exec cpcontainer sh -c 'echo "This second file is on the container as well" > /srv/subdir/containerfile2' + # "." and "dir/." will copy the contents, so make sure that a dir ending + # with dot is treated correctly. + run_podman exec cpcontainer sh -c 'mkdir /tmp/subdir.; cp /srv/subdir/* /tmp/subdir./' run_podman cp cpcontainer:/srv $srcdir run cat $srcdir/srv/subdir/containerfile1 @@ -174,6 +192,14 @@ load helpers is "$output" "This first file is on the container" run cat $srcdir/containerfile2 is "$output" "This second file is on the container as well" + rm -rf $srcdir/subdir + + run_podman cp cpcontainer:/tmp/subdir. $srcdir + run cat $srcdir/subdir./containerfile1 + is "$output" "This first file is on the container" + run cat $srcdir/subdir./containerfile2 + is "$output" "This second file is on the container as well" + rm -rf $srcdir/subdir. run_podman rm -f cpcontainer } |