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 {