From 34b06fa448afe2ce852cfcbcf8639f88b87a75a4 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 7 Feb 2024 20:02:26 +0100 Subject: [PATCH] confd: update container documentation and YANG models Due to the deprecation of CNI i Podman[1], and its upcoming removal in Podman v5.0, we decided to cut our losses and modify all documentation and models to become backend neutral. We will continue to use CNI as long as possible -- it gives us host interfaces, for instance, that is not part of netawark yet, it seems. Also, sync mount and volume documentation with YANG model changes. [1]: https://blog.podman.io/2023/11/cni-deprecation-and-removal-from-podman-5-0/ Signed-off-by: Joachim Wiberg --- doc/container.md | 69 +++++++++---------- src/confd/src/ietf-interfaces.c | 57 ++++++++------- .../yang/infix-containers@2024-02-01.yang | 12 ++-- .../yang/infix-if-container@2024-01-15.yang | 46 +++++++------ 4 files changed, 93 insertions(+), 91 deletions(-) diff --git a/doc/container.md b/doc/container.md index 01180e51..c1743f82 100644 --- a/doc/container.md +++ b/doc/container.md @@ -6,8 +6,8 @@ Containers in Infix * [Getting Started](#getting-started) * [Examples](#examples) * [Networking and Containers](#networking-and-containers) - * [CNI Bridge](#cni-bridge) - * [CNI Host](#cni-host) + * [Container Bridge](#container-bridge) + * [Container Host Interface](#container-host-interface) * [Host Networking](#host-networking) * [Mounts and Volumes](#mounts-and-volumes) * [Content Mounts](#content-mounts) @@ -31,7 +31,7 @@ is a rare and in many cases *unique* feature of Infix. All network specific settings are done using the IETF interfaces YANG model, with augments for containers to ensure smooth integration with -the Container Network Interface ([CNI][]) that podman supports. +container networking in podman. Caution @@ -104,9 +104,9 @@ Persistent web server using nginx, sharing the host's network: admin@example-c0-ff-ee:/config> edit container web admin@example-c0-ff-ee:/config/container/web> set image docker://nginx:alpine admin@example-c0-ff-ee:/config/container/web> set publish 80:80 - admin@example-c0-ff-ee:/config/container/web> set host-network + admin@example-c0-ff-ee:/config/container/web> set network host admin@example-c0-ff-ee:/config/container/web> leave - admin@example-c0-ff-ee:/> show container + admin@example-c0-ff-ee:/> show container Exit to the shell and verify the service with curl, or try to attach to your device's IP address using your browser: @@ -123,7 +123,7 @@ Networking and Containers By default, unlike other systems, persistent[^1] containers have no networking enabled. All network access has to be set up explicitly. -Currently two types of of [CNI][] networks are supported: +Currently two types of of container networks are supported: - `cni-host`: one end of a VETH pair, or a physical Ethernet port - `cni-bridge`: an IP masquerading bridge @@ -175,7 +175,8 @@ the container. The default, after each stop/start cycle, or reboot of the host, is to name the interfaces `eth0`, `eth1`, in the order they are given in the `network` list, and to give the container the next address in a `cni-bridge`. Below an example of a system container calls -`set network docker0`, here we show how to set options for that network: +`set network interface docker0`, here we show how to set options for +that network: admin@example-c0-ff-ee:/config/container/ntpd/> edit network docker0 admin@example-c0-ff-ee:/config/container/ntpd/network/docker0/> @@ -205,13 +206,12 @@ pair as an example along with a regular bridge (where other Ethernet interfaces may live as well). admin@example-c0-ff-ee:/config/> edit interface veth0 - admin@example-c0-ff-ee:/config/interface/veth0a/> set veth peer ntpd - admin@example-c0-ff-ee:/config/interface/veth0a/> set ipv4 address 192.168.0.1 prefix-length 24 - admin@example-c0-ff-ee:/config/interface/veth0a/> end + admin@example-c0-ff-ee:/config/interface/veth0/> set veth peer ntpd + admin@example-c0-ff-ee:/config/interface/veth0/> set ipv4 address 192.168.0.1 prefix-length 24 + admin@example-c0-ff-ee:/config/interface/veth0/> end admin@example-c0-ff-ee:/config/> edit interface ntpd admin@example-c0-ff-ee:/config/interface/ntpd/> set ipv4 address 192.168.0.2 prefix-length 24 admin@example-c0-ff-ee:/config/interface/ntpd/> set container-network - admin@example-c0-ff-ee:/config/interface/ntpd/container-network/> set This is a routed setup, where we reserve 192.168.0.0/24 for the network between the host and the `ntpd` container. A perhaps more common case @@ -242,14 +242,14 @@ file system: admin@example-c0-ff-ee:/config/container/system/> edit mount leds admin@example-c0-ff-ee:/config/container/system/mount/leds> set source /sys/class/leds - admin@example-c0-ff-ee:/config/container/system/mount/leds> set destination /sys/class/leds + admin@example-c0-ff-ee:/config/container/system/mount/leds> set target /sys/class/leds admin@example-c0-ff-ee:/config/container/system/mount/leds> end admin@example-c0-ff-ee:/config/container/system/> Sometimes *volumes* are a better fit. A volume is an automatically created read-writable entity that follows the life of your container. - admin@example-c0-ff-ee:/config/container/ntpd/> set volume varlib destination /var/lib + admin@example-c0-ff-ee:/config/container/ntpd/> set volume varlib target /var/lib Volumes survive reboots and upgrading of the base image, unlike the persistent writable layer you get by default, which does not survive @@ -273,7 +273,7 @@ file is created using the decoded base64 data from the `content` node. admin@example-c0-ff-ee:/config/container/ntpd/> edit mount ntpd.conf admin@example-c0-ff-ee:/config/container/ntpd/mount/ntpd.conf> set content ... interactive editor starts up ... - admin@example-c0-ff-ee:/config/container/ntpd/mount/ntpd.conf> set destination /etc/ntpd.conf + admin@example-c0-ff-ee:/config/container/ntpd/mount/ntpd.conf> set target /etc/ntpd.conf admin@example-c0-ff-ee:/config/container/ntpd/mount/ntpd.conf> end admin@example-c0-ff-ee:/config/container/ntpd/> @@ -298,7 +298,7 @@ we created previously: admin@example-c0-ff-ee:/> configure admin@example-c0-ff-ee:/config> edit container system admin@example-c0-ff-ee:/config/container/system/> set image ghcr.io/kernelkit/curios:edge - admin@example-c0-ff-ee:/config/container/system/> set network docker0 + admin@example-c0-ff-ee:/config/container/system/> set network interface docker0 admin@example-c0-ff-ee:/config/container/system/> set publish 222:22 admin@example-c0-ff-ee:/config/container/system/> leave @@ -314,7 +314,7 @@ container configuration context for the full syntax.) Available containers can be accessed from admin-exec: - admin@example-c0-ff-ee:/> show container + admin@example-c0-ff-ee:/> show container CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 439af2917b44 ghcr.io/kernelkit/curios:edge 41 hours ago Up 16 hours 0.0.0.0:222->222/tcp system @@ -322,18 +322,18 @@ This is a system container, so you can "attach" to it by starting a shell (or logging in with SSH): admin@example-c0-ff-ee:/> container shell system - root@439af2917b44:/# + root@439af2917b44:/# Notice how the hostname inside the container changes. By default the container ID (hash) is used, but this can be easily changed: root@439af2917b44:/# exit - admin@infix-00-00-00:/> configure - admin@infix-00-00-00:/config/> edit container system - admin@infix-00-00-00:/config/container/system/> set hostname system1 - admin@infix-00-00-00:/config/container/system/> leave - admin@infix-00-00-00:/> container shell system - root@system1:/# + admin@infix-00-00-00:/> configure + admin@infix-00-00-00:/config/> edit container system + admin@infix-00-00-00:/config/container/system/> set hostname sys101 + admin@infix-00-00-00:/config/container/system/> leave + admin@infix-00-00-00:/> container shell system + root@sys101:/# [^1]: this does not apply to the admin-exec command `container run`. This command is intended to be used for testing and evaluating @@ -350,12 +350,12 @@ For really advanced setups, the following will be the only alternative: admin@example-c0-ff-ee:/> configure admin@example-c0-ff-ee:/config> edit container nftables admin@example-c0-ff-ee:/config/container/system/> set image ghcr.io/kernelkit/curios-nftables:edge - admin@example-c0-ff-ee:/config/container/system/> set host-network - admin@example-c0-ff-ee:/config/container/system/> edit file nftables.conf - admin@example-c0-ff-ee:/config/container/system/file/nftables.conf/> set path /etc/nftables.conf - admin@example-c0-ff-ee:/config/container/system/file/nftables.conf/> set content + admin@example-c0-ff-ee:/config/container/system/> set network host + admin@example-c0-ff-ee:/config/container/system/> edit mount nftables.conf + admin@example-c0-ff-ee:/config/container/system/mount/nftables.conf/> set target /etc/nftables.conf + admin@example-c0-ff-ee:/config/container/system/mount/nftables.conf/> set content ... interactive editor starts up where you can paste your rules ... - admin@example-c0-ff-ee:/config/container/system/file/nftables.conf/> leave + admin@example-c0-ff-ee:/config/container/system/mount/nftables.conf/> leave ### Application Container: ntpd @@ -373,14 +373,14 @@ use volumes in this example. admin@example-c0-ff-ee:/> configure admin@example-c0-ff-ee:/config> edit container ntpd admin@example-c0-ff-ee:/config/container/ntpd/> set image ghcr.io/kernelkit/curios-ntpd:edge - admin@example-c0-ff-ee:/config/container/ntpd/> set network ntpd # From veth0 above - admin@example-c0-ff-ee:/config/container/ntpd/> edit file ntp.conf - admin@example-c0-ff-ee:/config/container/ntpd/file/ntp.conf/> set path /etc/ntp.conf - admin@example-c0-ff-ee:/config/container/ntpd/file/ntp.conf/> set content + admin@example-c0-ff-ee:/config/container/ntpd/> set network interface ntpd # From veth0 above + admin@example-c0-ff-ee:/config/container/ntpd/> edit mount ntp.conf + admin@example-c0-ff-ee:/config/container/ntpd/mount/ntp.conf/> set target /etc/ntp.conf + admin@example-c0-ff-ee:/config/container/ntpd/mount/ntp.conf/> set content ... interactive editor starts up where you can paste your rules ... - admin@example-c0-ff-ee:/config/container/ntpd/file/ntp.conf/> end + admin@example-c0-ff-ee:/config/container/ntpd/mount/ntp.conf/> end admin@example-c0-ff-ee:/config/container/ntpd/> edit volume varlib - admin@example-c0-ff-ee:/config/container/ntpd/volume/varlib/> set destination /var/lib + admin@example-c0-ff-ee:/config/container/ntpd/volume/varlib/> set target /var/lib admin@example-c0-ff-ee:/config/container/ntpd/volume/varlib/> leave admin@example-c0-ff-ee:/> copy running-config startup-config @@ -414,6 +414,5 @@ restarted. [1]: https://github.com/kernelkit/infix/blob/main/src/confd/yang/infix-containers%402023-12-14.yang -[CNI]: https://www.cni.dev/ [2]: https://github.com/troglobit/mg [podman]: https://podman.io diff --git a/src/confd/src/ietf-interfaces.c b/src/confd/src/ietf-interfaces.c index d0a83100..258d8294 100644 --- a/src/confd/src/ietf-interfaces.c +++ b/src/confd/src/ietf-interfaces.c @@ -28,9 +28,9 @@ static bool iface_is_cni(const char *ifname, struct lyd_node *cif) { - struct lyd_node *cni = lydx_get_child(cif, "container-network"); + struct lyd_node *net = lydx_get_child(cif, "container-network"); - if (cni) + if (net) return true; return false; @@ -57,15 +57,14 @@ static void cni_gen_addrs(struct lyd_node *ip, FILE *fp, int *first) } #if 0 /* Unused for now, use container specific global dns and search settings instead. */ -static void cni_gen_dns(struct lyd_node *cni, FILE *fp, int *first) +static void cni_gen_dns(struct lyd_node *net, FILE *fp, int *first) { struct lyd_node *dns; - dns = lydx_get_child(cni, "dns"); + dns = lydx_get_child(net, "dns"); if (dns) { struct lyd_node *node; - puts("Adding DNS to CNI profile!"); fprintf(fp, ",\n \"dns\": {"); *first = 1; @@ -75,7 +74,6 @@ static void cni_gen_dns(struct lyd_node *cni, FILE *fp, int *first) else fprintf(fp, ", "); - printf("Adding DNS nameserver %s to CNI profile!\n", lyd_get_value(node)); fprintf(fp, "\"%s\"", lyd_get_value(node)); (*first)++; } @@ -89,7 +87,7 @@ static void cni_gen_dns(struct lyd_node *cni, FILE *fp, int *first) (*first)++; } - LYX_LIST_FOR_EACH(lyd_child(cni), node, "search") { + LYX_LIST_FOR_EACH(lyd_child(net), node, "search") { if (*first) fprintf(fp, "%s\n \"search\": [ ", *first > 1 ? "," : ""); else @@ -108,9 +106,9 @@ static void cni_gen_dns(struct lyd_node *cni, FILE *fp, int *first) * Set up IP masquerading bridge which acts as a gateway for nodes behind it. * Default subnet, if one is missing in configuration, is: 10.88.0.0/16 */ -static int cni_bridge(struct lyd_node *cni, const char *ifname) +static int cni_bridge(struct lyd_node *net, const char *ifname) { - struct lyd_node *net; + struct lyd_node *node; int first = 1; FILE *fp; @@ -132,9 +130,9 @@ static int cni_bridge(struct lyd_node *cni, const char *ifname) " \"ipam\": {\n" " \"type\": \"host-local\"", ifname, ifname); - LYX_LIST_FOR_EACH(lyd_child(cni), net, "route") { - struct lyd_node *subnet = lydx_get_child(net, "subnet"); - struct lyd_node *gateway = lydx_get_child(net, "gateway"); + LYX_LIST_FOR_EACH(lyd_child(net), node, "route") { + struct lyd_node *subnet = lydx_get_child(node, "subnet"); + struct lyd_node *gateway = lydx_get_child(node, "gateway"); if (first) fprintf(fp, ",\n \"routes\": [\n"); @@ -155,9 +153,9 @@ static int cni_bridge(struct lyd_node *cni, const char *ifname) fprintf(fp, ",\n \"routes\": [ { \"dst\": \"0.0.0.0/0\" } ]"); first = 1; - LYX_LIST_FOR_EACH(lyd_child(cni), net, "subnet") { - struct lyd_node *subnet = lydx_get_child(net, "subnet"); - struct lyd_node *gateway = lydx_get_child(net, "gateway"); + LYX_LIST_FOR_EACH(lyd_child(net), node, "subnet") { + struct lyd_node *subnet = lydx_get_child(node, "subnet"); + struct lyd_node *gateway = lydx_get_child(node, "gateway"); if (first) fprintf(fp, ",\n \"ranges\": [\n"); @@ -202,9 +200,9 @@ static int cni_bridge(struct lyd_node *cni, const char *ifname) return 0; } -static int cni_host(struct lyd_node *cni, const char *ifname) +static int cni_host(struct lyd_node *net, const char *ifname) { - struct lyd_node *net, *ip; + struct lyd_node *node, *ip; int first = 1; FILE *fp; @@ -223,11 +221,11 @@ static int cni_host(struct lyd_node *cni, const char *ifname) " \"type\": \"static\"", ifname, ifname); - ip = lydx_get_child(lyd_parent(cni), "ipv4"); + ip = lydx_get_child(lyd_parent(net), "ipv4"); if (ip) cni_gen_addrs(ip, fp, &first); - ip = lydx_get_child(lyd_parent(cni), "ipv6"); + ip = lydx_get_child(lyd_parent(net), "ipv6"); if (ip) cni_gen_addrs(ip, fp, &first); @@ -235,9 +233,9 @@ static int cni_host(struct lyd_node *cni, const char *ifname) fprintf(fp, "\n ]"); first = 1; - LYX_LIST_FOR_EACH(lyd_child(cni), net, "route") { - struct lyd_node *subnet = lydx_get_child(net, "subnet"); - struct lyd_node *gateway = lydx_get_child(net, "gateway"); + LYX_LIST_FOR_EACH(lyd_child(net), node, "route") { + struct lyd_node *subnet = lydx_get_child(node, "subnet"); + struct lyd_node *gateway = lydx_get_child(node, "gateway"); if (first) fprintf(fp, ",\n \"routes\": [\n"); @@ -269,17 +267,16 @@ static int cni_host(struct lyd_node *cni, const char *ifname) static int iface_gen_cni(const char *ifname, struct lyd_node *cif) { - struct lyd_node *cni = lydx_get_child(cif, "container-network"); - const char *type = lydx_get_cattr(cni, "type"); + struct lyd_node *net = lydx_get_child(cif, "container-network"); + const char *type = lydx_get_cattr(net, "type"); - ERROR("Got CNI %s iface %s", type, ifname); - if (!strcmp(type, "cni-host")) - return cni_host(cni, ifname); + if (!strcmp(type, "host")) + return cni_host(net, ifname); - if (!strcmp(type, "cni-bridge")) - return cni_bridge(cni, ifname); + if (!strcmp(type, "bridge")) + return cni_bridge(net, ifname); - ERROR("Unknown CNI interface type %s, skipping.", type); + ERROR("Unknown container network type %s, skipping.", type); return 0; } diff --git a/src/confd/yang/infix-containers@2024-02-01.yang b/src/confd/yang/infix-containers@2024-02-01.yang index c09fe72e..c9f2d051 100644 --- a/src/confd/yang/infix-containers@2024-02-01.yang +++ b/src/confd/yang/infix-containers@2024-02-01.yang @@ -165,7 +165,7 @@ module infix-containers { key name; leaf name { - description "CNI network (interface name) to connect to the container."; + description "Container network (interface name) to connect to the container."; type if:interface-ref; must "/if:interfaces/if:interface[if:name = current()]/infix-if:container-network" { error-message "Container networks must be interfaces classified as container-network."; @@ -173,16 +173,20 @@ module infix-containers { } leaf-list option { - description "Options for CNI bridges. - Example: ip=1.2.3.4 to request a specific IP, both IPv4 and IPv6. - interface_name=foo0 name to set interface name inside container."; + description "Options for a masquerading container bridges. + + Example: ip=1.2.3.4 -- request a specific IP (IPv4 or IPv6) + mac=00:01:02:c0:ff:ee -- set fixed MAC address in container + interface_name=foo0 -- set interface name inside container"; type string; } } leaf-list publish { description "Publish container port, or a range of ports, to the host. + Syntax: [[ip:][hostPort]:]containerPort[/protocol] + Sample: 8080:80 -- forward tcp port 8080 to container port 80 69:69/udp -- forward udp port 69 to container port 69 127.0.0.1:8080:80 -- forward only from loopback interface"; diff --git a/src/confd/yang/infix-if-container@2024-01-15.yang b/src/confd/yang/infix-if-container@2024-01-15.yang index f39d48ec..925122c2 100644 --- a/src/confd/yang/infix-if-container@2024-01-15.yang +++ b/src/confd/yang/infix-if-container@2024-01-15.yang @@ -16,9 +16,9 @@ submodule infix-if-container { organization "KernelKit"; contact "kernelkit@googlegroups.com"; - description "CNI network to interface mapper extension for ietf-interfaces. - This ensures a container interface can never be a bridge port, - or LAG member at the same time."; + description "Container network to interface mapping for ietf-interfaces. + Ensures a container interface can never be a bridge port, or + LAG member, at the same time."; revision 2024-01-15 { description "Initial revision."; @@ -29,18 +29,18 @@ submodule infix-if-container { * Identities */ - identity cni-type { - description "CNI network type"; + identity container-network { + description "Container network type"; } - identity cni-bridge { - base cni-type; - description "CNI standard bridge with IP masquerading, portmappping, and firewalling."; + identity bridge { + base container-network; + description "Container bridge with IP masquerading, portmappping, and firewalling."; } - identity cni-host { - base cni-type; - description "CNI host-device, e.g., one end of a VETH pair or other host interface."; + identity host { + base container-network; + description "Host device, e.g., one end of a VETH pair or other host interface."; } /* @@ -48,31 +48,32 @@ submodule infix-if-container { */ augment "/if:interfaces/if:interface/infix-if:port" { - description "Augments the interface model with CNI networks."; + description "Augments the interface model with container networks."; if-feature containers; case container-network { container container-network { - presence "Container network (CNI) mapping."; + presence "Container network mapping."; leaf type { - description "Bridge or host interface"; + description "Masquerading container bridge or a host interface"; type identityref { - base cni-type; + base container-network; } - default cni-host; } list subnet { description "Static IP ranges to hand out addresses to containers from. - A cni-bridge will forward DNS, NTP, and SSH by default to + + A container bridge forwards DNS, NTP, and SSH by default to the host interfaces."; - when "../type = 'infix-if:cni-bridge'"; + when "../type = 'infix-if:bridge'"; key subnet; leaf subnet { type inet:ip-prefix; description "Subnet to assign addresses from, round-robin assignment. + The default is from a standard Docker setup."; default "172.17.0.0/16"; } @@ -80,16 +81,17 @@ submodule infix-if-container { leaf gateway { type inet:ip-address; description "Optional gateway address for the subnet, defaults to .1. - This will be used as the address of a cni-bridge."; + + This will be used as the address of the container bridge."; } } list route { description "IPv4 or IPv6 routes to be added to container. - For cni-bridge type interfaces the gateway can be omitted, - the the IP address of the CNI bridge will then be used as - the next-hop address."; + For bridge type interfaces the gateway can be omitted, the + IP address of the container bridge will then be used as the + next-hop address."; key subnet; leaf subnet {