From 10a030e2a049ca100262a76f22af8f93073f700d Mon Sep 17 00:00:00 2001 From: joshrosso Date: Sun, 17 Apr 2022 06:58:43 -0600 Subject: [PATCH 1/2] navigating oci assets --- content/c/navigating-oci.md | 501 ++++++++++++++++++++++++++++++++++++ 1 file changed, 501 insertions(+) create mode 100644 content/c/navigating-oci.md diff --git a/content/c/navigating-oci.md b/content/c/navigating-oci.md new file mode 100644 index 0000000..aad5b31 --- /dev/null +++ b/content/c/navigating-oci.md @@ -0,0 +1,501 @@ +--- +title: "Navigating OCI Artifacts" +weight: 9910 +description: OCI has long been the way we package container images. However, we've found this standard to host more and more assets over time. Examples include Helm charts and other configuration. In this post, I'll explore how we can navigate, introspect, and mutate these OCI artifacts with a tool independent from a container runtime. +date: 2022-04-18 +images: +- https://octetz.s3.us-east-2.amazonaws.com/yt_book_graphic.png +aliases: +- /latest +--- + +# Navigating OCI Artifacts + +OCI has long been the way we package container images. However, we've found this +standard to host more and more assets over time. Examples include Helm charts +and other configuration. In this post, I'll explore how we can navigate, +introspect, and mutate these OCI artifacts with a tools you can use independent +from a container runtime. + +## Discovering, Introspecting, and Mutating + +For discovering and introspecting artifacts, I use `crane`. It is both a command +line tool and excellent (Go) library from interacting with OCI artifacts in +repositories. I use this tool weekly as my Swiss Army Knife for OCI-related +operations. Since `crane` is independent of a container runtime, I find it +especially helpful in troubleshooting the assets used by container runtime, like +Docker. + +Let's start with a use-case where you'd like to understand which repos are +available in a registry. This can be fairly complex as projects like Harbor +support the idea of embedding projects into projects. For example, all of these +are valid repositories. + +* /project-a +* /project-a/asset-b +* /project-a/asset-c/ + +To start off, lets take a look at VMware's public-facing Harbor instance: + +```sh +$ crane catalog projects.registry.vmware.com/tce +TODO(joshrosso): output here +``` + +If you know which repository/project you want to interact with, another common +struggle is figuring out which tags are available. Most UIs like DockerHub or +Harbor can be a bit obnoxious when trying to determine tags, so I often rely on: + +```sh +$ crane ls index.docker.io/nginx +1.20.1-alpine-perl +1.20.1-perl +1.20.2 +1.20.2-alpine +1.20.2-alpine-perl +1.20.2-perl +1.21 +1.21-alpine +1.21-alpine-perl +1.21-perl +1.21.0 +1.21.0-alpine +1.21.0-alpine-perl +1.21.0-perl +1.21.1 +1.21.1-alpine +1.21.1-alpine-perl +1.21.1-perl +1.21.3 +1.21.3-alpine +1.21.3-alpine-perl +1.21.3-perl +1.21.4 +1.21.4-alpine +1.21.4-alpine-perl +1.21.4-perl +1.21.5 +1.21.5-alpine +1.21.5-alpine-perl +1.21.5-perl +1.21.6 +1.21.6-alpine +1.21.6-alpine-perl +1.21.6-perl +``` + +> Results above trimmed for brevity. + +Now that you've got a list of tags, it would be cool to understand what +underlies a tag. Perhaps you don't yet wish to pull down all the contents, but +more so want to look at the image's manifest to better understand its contents. + +```sh +$ crane manifest index.docker.io/nginx:1.21.6 | jq +``` + +The resulting JSON is: + +```json +{ + "manifests": [ + { + "digest": "sha256:83d487b625d8c7818044c04f1b48aabccd3f51c3341fc300926846bca0c439e6", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "amd64", + "os": "linux" + }, + "size": 1570 + }, + { + "digest": "sha256:3100debc8e667aba0a8284f8cff4b209c941a061f2fb07ea8ab97a96c6caec17", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "arm", + "os": "linux", + "variant": "v5" + }, + "size": 1570 + }, + { + "digest": "sha256:b47feab811a96a3b2c6f0ea8c0bb17314c2cf26d7fdbf678764ca1835f96b3c2", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "arm", + "os": "linux", + "variant": "v7" + }, + "size": 1570 + }, + { + "digest": "sha256:29fdd887bf4bb4ffe0f3e25893b5c452dad098c95d12cf4ae992542461bcb6f4", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "arm64", + "os": "linux", + "variant": "v8" + }, + "size": 1570 + }, + { + "digest": "sha256:5267ae8ff9dc9acdac28e0c927829992d8fb6b12933e73834da07ce61eda6794", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "386", + "os": "linux" + }, + "size": 1570 + }, + { + "digest": "sha256:91961afa3560f93632f9aa18fb694b9d28c9ac351363adf097dd00dac372a7a2", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "mips64le", + "os": "linux" + }, + "size": 1570 + }, + { + "digest": "sha256:df9d138f285fc6175a2c1830a8f7fe00908d9a9bdd59c5e7055b428c0f5dc5fe", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "ppc64le", + "os": "linux" + }, + "size": 1570 + }, + { + "digest": "sha256:8b9b431c2e25ded597edb98589b701f4c1f907739b9491736ae10d9b96acf57f", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "s390x", + "os": "linux" + }, + "size": 1570 + } + ], + "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "schemaVersion": 2 +} +``` + +In the above manifest list, we can see this image supports multiple +architectures! So we'll know that if a container runtime pulls down this tag, +it'll pull down one of the manifests in the list based on its architecture. For +example, we'd expect `containerd` running on an ARM Linux host to pull down +`index.docker.io/nginx@sha256:3100debc8e667aba0a8284f8cff4b209c941a061f2fb07ea8ab97a96c6caec17`. + +Now it is time to pull down an actual image. Container images are essentially +file systems that get tar'ed up and stored. We can reveal this easily by +exporting the contents of an OCI artifact. + +```sh +$ crane export index.docker.io/nginx:v1.22.5 - | tar xv +``` + +By default, `crane export` would print the contents to standard out. Instead, we +want to take those `tar` contents and pipe them through `tar xv` to unpack them +on the local file system. Looking at the content, we can see this is a Linux +file system representing the container image. + +```sh +$ tree -L2 bin/ var/ + +bin/ +├── bash +├── cat +├── chgrp +├── chmod +├── chown +├── cp +├── dash +├── date +├── dd +├── df +├── dir +├── dmesg +├── dnsdomainname -> hostname +├── domainname -> hostname +├── echo +├── egrep +├── false +├── fgrep +├── findmnt +├── grep +├── gunzip +├── gzexe +├── gzip +├── hostname +├── ln +├── login +├── ls +├── lsblk +├── mkdir +├── mknod +├── mktemp +├── more +├── mount +├── mountpoint +├── mv +├── nisdomainname -> hostname +├── pidof -> /sbin/killall5 +├── pwd +├── rbash -> bash +├── readlink +├── rm +├── rmdir +├── run-parts +├── sed +├── sh -> dash +├── sleep +├── stty +├── su +├── sync +├── tar +├── tempfile +├── touch +├── true +├── umount +├── uname +├── uncompress +├── vdir +├── wdctl +├── ypdomainname -> hostname +├── zcat +├── zcmp +├── zdiff +├── zegrep +├── zfgrep +├── zforce +├── zgrep +├── zless +├── zmore +└── znew +var/ +├── backups +├── cache +│   ├── apt +│   ├── debconf +│   ├── ldconfig +│   └── nginx +├── lib +│   ├── apt +│   ├── dpkg +│   ├── misc +│   ├── pam +│   ├── systemd +│   └── ucf +├── local +├── lock -> /run/lock +├── log +│   ├── apt +│   ├── btmp +│   ├── dpkg.log +│   ├── faillog +│   ├── lastlog +│   ├── nginx +│   └── wtmp +├── mail +├── opt +├── run -> /run +├── spool +│   └── mail -> ../mail +└── tmp + +24 directories, 5 files +``` + +Copying images between repositories is another useful action. For example, +consider the need to "relocate" an image into a registry you're hosting. A cool +capability of `crane` is its ability to copy all architectures of an image and +maintain all the digest values in the manifest. + +```sh +$ crane cp index.docker.io/nginx:1.21.6 index.docker.io/joshrosso/nginx +2022/04/16 07:10:51 Copying from index.docker.io/nginx:1.21.6 to index.docker.io/joshrosso/nginx +2022/04/16 07:10:52 mounted blob: sha256:18f38162c0ce5b57d4967e6b8f863b52243b5796aeb45dcf761c5b21feef0984 +2022/04/16 07:10:52 mounted blob: sha256:08c3cb2073f14645ef019dc8e97b90682742fe1d034559189bf579ce783aa9a4 +2022/04/16 07:10:52 mounted blob: sha256:2215908dc0a28873ff92070371b1ba3a3cb9d4440d44926c5f29f47a76b17b35 +2022/04/16 07:10:52 mounted blob: sha256:c229119241af7b23b121052a1cae4c03e0a477a72ea6a7f463ad7623ff8f274b +2022/04/16 07:10:52 mounted blob: sha256:10e2168f148a67a2fcd6078a3910f234f4378c15d8e2070b079a3ad869e73f15 +2022/04/16 07:10:53 mounted blob: sha256:c4ffe9532b5f9277da1c9573e40f56c4b0aacf819b52f4949b7f786b83a24e62 +2022/04/16 07:10:53 pushed blob: sha256:12766a6745eea133de9fdcd03ff720fa971fdaf21113d4bc72b417c123b15619 +2022/04/16 07:10:53 index.docker.io/joshrosso/nginx@sha256:83d487b625d8c7818044c04f1b48aabccd3f51c3341fc300926846bca0c439e6: digest: sha256:83d487b625d8c7818044c04f1b48aabccd3f51c3341fc300926846bca0c439e6 size: 1570 +2022/04/16 07:10:54 mounted blob: sha256:9a41aba0a099ec129c20f41f6370b97daa4c3d4d3edc76ea1863bc5f76f9e5e5 +2022/04/16 07:10:54 mounted blob: sha256:37e08739adf0232f999fad1e4495755970c2358af62196d94b5fa1bf58e463e1 +2022/04/16 07:10:54 mounted blob: sha256:c6812c30b6d2af8f825b217b6833b5334ec96d6062f399f7de6fefc170440b1d +2022/04/16 07:10:54 mounted blob: sha256:9fe7e8d810837eca0b2abac6ac6ae94c6634bd3e330a68a01b0ab6cec489ebb2 +2022/04/16 07:10:54 mounted blob: sha256:02b6b00af27f3727a8015a9b74bec86a59d35bb4edc3176366acd59e10153ddb +2022/04/16 07:10:54 mounted blob: sha256:5cccdc24373a67b0a456476a0bae185f2b9a25a7af8b9d4112eb8d9ab5571360 +2022/04/16 07:10:55 pushed blob: sha256:f751231ef8e6225f2fa9c4360c7ec23564037ae914b2f8dda6edf739d2c6d620 +2022/04/16 07:10:55 index.docker.io/joshrosso/nginx@sha256:3100debc8e667aba0a8284f8cff4b209c941a061f2fb07ea8ab97a96c6caec17: digest: sha256:3100debc8e667aba0a8284f8cff4b209c941a061f2fb07ea8ab97a96c6caec17 size: 1570 +2022/04/16 07:10:56 mounted blob: sha256:f98812e1a494a683a5b3dea593dd2ef305f5f732193044c147f22e44b00164bc +2022/04/16 07:10:56 mounted blob: sha256:f1ece336d115ef70c93df1103198b7b522c5afa79884792da6f67450044dfb05 +2022/04/16 07:10:56 mounted blob: sha256:abcb63b666dd6f3bd284849e8d740e078cd526e4d15296963bfa603f3c0316b6 +2022/04/16 07:10:56 mounted blob: sha256:31db52cedfb75607a7b1ca98c9e81990d9dd4f60d1cd94a444e4514cc0b02a56 +2022/04/16 07:10:56 mounted blob: sha256:b490d27fdd4a7493a6330a4d9abeec5847aa2a09ec5d78f217a25d956eb06c3e +2022/04/16 07:10:56 mounted blob: sha256:3a499091a266645a8a1e0441588584b3d6ed5bedaa53baee988f582eafc2bb01 +2022/04/16 07:10:58 pushed blob: sha256:04279fb2ac73e6b6f4d1a92f4563234e83d95ecfa31e63d828f3eef064f27f22 +2022/04/16 07:10:58 index.docker.io/joshrosso/nginx@sha256:b47feab811a96a3b2c6f0ea8c0bb17314c2cf26d7fdbf678764ca1835f96b3c2: digest: sha256:b47feab811a96a3b2c6f0ea8c0bb17314c2cf26d7fdbf678764ca1835f96b3c2 size: 1570 +2022/04/16 07:10:58 mounted blob: sha256:2203022c5aa978ec114a15a7cdc2c323c65922d3b0a8eab11d50811bb9ae1cfb +2022/04/16 07:10:58 mounted blob: sha256:48ac378ef1156001ee2c1627a92ac394b9dfa757e0f7b4c35dcb3115b5935230 +2022/04/16 07:10:59 mounted blob: sha256:b14df6d5899439911e5a79317752118a8d3783cf7426fd211d142e95d87f914e +2022/04/16 07:10:59 mounted blob: sha256:93fe00d45e28ec8d63ba6aa7373414fdaac46f6d644499fefd208a60bda383a0 +2022/04/16 07:10:59 mounted blob: sha256:fc0b36f3f49a15683556e499a0c18433e60769a376167b396248240d09bd0529 +2022/04/16 07:10:59 mounted blob: sha256:4dd9061d5c104129ba1a14b5ac11d820e303b18672d73b688dcb3feb1adf2fe0 +2022/04/16 07:10:59 pushed blob: sha256:fd3d31a07ae69fb788a579676d2c5f4c3dd201f57bcd6c174cd0bd6475886f23 +2022/04/16 07:11:00 index.docker.io/joshrosso/nginx@sha256:29fdd887bf4bb4ffe0f3e25893b5c452dad098c95d12cf4ae992542461bcb6f4: digest: sha256:29fdd887bf4bb4ffe0f3e25893b5c452dad098c95d12cf4ae992542461bcb6f4 size: 1570 +2022/04/16 07:11:00 mounted blob: sha256:fec59da75229f638ca2878278d3859a1a8b73a20d5c0c043354eb37129ebb8bf +2022/04/16 07:11:00 mounted blob: sha256:f43e9a945a87cbe5e09776aec1972e95ae1197e05aca746a6aff3e654fa71abf +2022/04/16 07:11:00 mounted blob: sha256:c5760117e1158b2f2caa10e67565c19a0a5a5ff9d50326fa7a7353e32138cfcf +2022/04/16 07:11:00 mounted blob: sha256:2d7ddcbfe894748c5af6664edb6a1e3f8571410b7bda2b59d31748f445317676 +2022/04/16 07:11:00 mounted blob: sha256:81801a23abac006775adcf44fa5586d618f8c20bd398462ec8bc58c8ef25e41a +2022/04/16 07:11:01 mounted blob: sha256:22eb59c3fa8cdaee9eb80cc3f2e804fca0d2a6c6eab50ae6170645a4cc3bb53d +2022/04/16 07:11:01 pushed blob: sha256:0cccf1347d1e22ac5d82202a0a259a5b1c7449f6db655b3a62d7a2be2c080ca9 +2022/04/16 07:11:02 index.docker.io/joshrosso/nginx@sha256:5267ae8ff9dc9acdac28e0c927829992d8fb6b12933e73834da07ce61eda6794: digest: sha256:5267ae8ff9dc9acdac28e0c927829992d8fb6b12933e73834da07ce61eda6794 size: 1570 +2022/04/16 07:11:02 mounted blob: sha256:5c2a8045f9de06328ab3d0ff505d990892219b7faee393bc9ac342347fc83d04 +2022/04/16 07:11:02 mounted blob: sha256:68b33e7c9b79a86225cc1cceb0823fad18c330ebc7362b4349d8b2935707511b +2022/04/16 07:11:02 mounted blob: sha256:62367482d35b25506e3cc68dc8dd0e224557731b7513a6d6891253fe5215ba07 +2022/04/16 07:11:02 mounted blob: sha256:f5981d2499580424d4e141e581514702261f6e69a1a7515d5a9ede0852df75fe +2022/04/16 07:11:02 mounted blob: sha256:c6824efc14a34f28f8c1ed89496eb77fbd141050509fa262f156e3b9efc9587c +2022/04/16 07:11:02 mounted blob: sha256:540b93cfb385a1b39b78d75f7e5232dae89742d78b9ec6ae3c18679bfdd565c0 +2022/04/16 07:11:03 pushed blob: sha256:29f287568fce0a3078142cadc265ddfe1d7b25a6757dfb798f79519f89bf2da9 +2022/04/16 07:11:04 index.docker.io/joshrosso/nginx@sha256:91961afa3560f93632f9aa18fb694b9d28c9ac351363adf097dd00dac372a7a2: digest: sha256:91961afa3560f93632f9aa18fb694b9d28c9ac351363adf097dd00dac372a7a2 size: 1570 +2022/04/16 07:11:04 mounted blob: sha256:ecc74bb8af5a048e1123af0e17d88ef3da1d10951ada79e8e1cc9c0a694245d3 +2022/04/16 07:11:04 mounted blob: sha256:79b897a9d589b42bd57e470df470107c267d73fbd1f4301e4c60f296625fae8b +2022/04/16 07:11:04 mounted blob: sha256:f922a9db7b077cd0b774883d903ceb9d44296f62ddd60464325b4964990120cc +2022/04/16 07:11:04 mounted blob: sha256:a152b63bfa87718b52c6147ab7aadeda59c663abb7b6b7325c23559160d7f1ca +2022/04/16 07:11:04 mounted blob: sha256:3f5f53f656bda967800c1ae2d6c59a8f9da4c4f9acb25a88cb6284f8776f46d1e +2022/04/16 07:11:04 mounted blob: sha256:738ca370c4d144edd240215fabd1164ce64b8e7e5e16151a239a7bacd83f7e0f +2022/04/16 07:11:06 pushed blob: sha256:5a6610cace171042d5b01f913cbfa39f278286a6723a976b09afcde57601103b +2022/04/16 07:11:06 index.docker.io/joshrosso/nginx@sha256:df9d138f285fc6175a2c1830a8f7fe00908d9a9bdd59c5e7055b428c0f5dc5fe: digest: sha256:df9d138f285fc6175a2c1830a8f7fe00908d9a9bdd59c5e7055b428c0f5dc5fe size: 1570 +2022/04/16 07:11:06 mounted blob: sha256:97996c57525a5d3a24c046a286920e97474273ef2b0115640a6d88faf2d68307 +2022/04/16 07:11:06 mounted blob: sha256:854034f6eae36d9a799222f23c2dc953f24d6822772d1de8016157c0d882ef7b +2022/04/16 07:11:06 mounted blob: sha256:ffb22bcde95509bb75f6dd2d69f3fdb5c7471727e4d720b31d92cd297582865c +2022/04/16 07:11:06 mounted blob: sha256:e2160ff505432841419414a1f375ff9e89fda5b83da0822e445aa85de9701f5a +2022/04/16 07:11:07 mounted blob: sha256:db35536e43c9f74e24474abae01ff9e157496d7d135125b5803ab1995cc003ca +2022/04/16 07:11:07 mounted blob: sha256:48c26db9479e034e61c00be5d13b25ba4e60f52f0475dcd4f8a4eddd29946e18 +2022/04/16 07:11:07 pushed blob: sha256:b15441cd379824f980596b11a5dd4c68d9e1c0aee3a66714fa8ffbb558d9c15e +2022/04/16 07:11:08 index.docker.io/joshrosso/nginx@sha256:8b9b431c2e25ded597edb98589b701f4c1f907739b9491736ae10d9b96acf57f: digest: sha256:8b9b431c2e25ded597edb98589b701f4c1f907739b9491736ae10d9b96acf57f size: 1570 +2022/04/16 07:11:08 index.docker.io/joshrosso/nginx: digest: sha256:2275af0f20d71b293916f1958f8497f987b8d8fd8113df54635f2a5915002bf1 size: 1862 +``` + +Running a quick `diff` against the remote manifests will reveal they are +identical. + +```sh +$ COPY=$(crane manifest index.docker.io/joshrosso/nginx) \ + ORIG=$(crane manifest index.docker.io/nginx) \ + diff <(echo ${COPY}) <(echo ${ORIG}) + +``` + +## Packaging and Pushing Non-Container Images + +When building container images, I tend to use `docker` as [its buildx +functionality works well for multi-architecture +builds](https://docs.docker.com/desktop/multi-arch/). However, I also push +non-container images up as OCI images/bundles. To do this, I've grown fond of +using `imgpkg`. + +> At the time of writing, I'm employed by VMware, the primary company sponsoring +> imgpkg. Independent of that, I do really like this tool. + +Let's start off producing an arbitrary manifest. In this case, we'll make a +simple Kubernetes manifest. + +```sh +cat < manifest.yaml +apiVersion: v1 +kind: Pod +metadata: + name: busybox-sleep +spec: + containers: + - name: busybox + image: busybox:1.28 + args: + - sleep + - "1000000" +EOF +``` + +We can push this manifest up as a OCI image using `imgpkg`: + +``` +$ imgpkg push -f manifest.yaml -i index.docker.io/joshrosso/busy-manifest:v1.0.0 +file: manifest.yaml +Pushed 'index.docker.io/joshrosso/busy-manifest@sha256:f17d2a9e70dfcf3f3c3ab1fc4129eddbefff930eec1e39642e5ebb8af25e9f2b' +Succeeded +``` + +This can be pulled and unpacked using tools like `crane` and `imgpkg`. In this +model, we're starting to treat OCI-compatible repositories as file stores. The +carvel tools take the model a step further by attempting to do nuanced things +with these manifests. For example we can "lock" the container image specified in +the manifest using `kbld`. + +```sh +$ kbld -f manifest.yaml --lock-output .imgpkg/images.yml +resolve | final: busybox:1.28 -> index.docker.io/library/busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47 +--- +apiVersion: v1 +kind: Pod +metadata: + annotations: + kbld.k14s.io/images: | + - origins: + - resolved: + tag: "1.28" + url: busybox:1.28 + url: index.docker.io/library/busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47 + name: busybox-sleep +spec: + containers: + - args: + - sleep + - "1000000" + image: index.docker.io/library/busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47 + name: busybox + +Succeeded +``` + +> `.imgpkg` directory needs to be created prior to running the above. + +In the above, you can see `index.docker.io/busybox:1.28` resolved to +`index.docker.io/library/busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47`. +This is referencing the digest value (SHA) rather than a tag. Since tags can be +overwritten, it'll provide better guarantees around the integrity of the +container image. I'll now re-push this image. + +``` +$ imgpkg push -f . -b index.docker.io/joshrosso/busy-manifest:v1.0.1 +dir: . +dir: .imgpkg +file: .imgpkg/images.yml +file: manifest.yaml +Pushed 'index.docker.io/joshrosso/busy-manifest@sha256:ee44383618580abc6126422dea5a5ecfed6f940cf8b43bb01bbf9cebe09bdddf' +Succeeded +``` + +Some subtle differences in the push above include: + +1. `-b` is used instead of `-i` to say this should be a bundle, rather than + image (check the OCI spec for details on these). +1. I'm specifying the full directory, `.`, as the file (`-f`) so that the + `.imgpkg` contents are also pushed in the bundle. + +A final example of where `imgpkg` shines is its ability to **recursively** copy +all OCI assets to a new registry. Using + +TODO(joshrosos): do a full recursive copy + +## Shoutouts + +* [Thanks to the go-containerregistry maintainers for making awesome libraries and +tools like `crane` and `ko`. Ya'll rock.](TODO(https://github.com/google/go-containerregistry/graphs/contributors) + +* [Thanks to the Carvel maintainers for continuing to push to envelope in how we + think about OCI artifacts and the packaging of them](TODO(https://github.com/vmware-tanzu/carvel-imgpkg/graphs/contributors) From 09bee20c767534815faff53aa3ce000aac3bddda Mon Sep 17 00:00:00 2001 From: joshrosso Date: Tue, 19 Apr 2022 07:05:19 -0600 Subject: [PATCH 2/2] Add working with OCI artifacts post --- content/c/navigating-oci.md | 165 +++++------------------------------- 1 file changed, 22 insertions(+), 143 deletions(-) diff --git a/content/c/navigating-oci.md b/content/c/navigating-oci.md index aad5b31..2caad16 100644 --- a/content/c/navigating-oci.md +++ b/content/c/navigating-oci.md @@ -1,46 +1,29 @@ --- -title: "Navigating OCI Artifacts" +title: "Navigating Remote OCI Artifacts" weight: 9910 -description: OCI has long been the way we package container images. However, we've found this standard to host more and more assets over time. Examples include Helm charts and other configuration. In this post, I'll explore how we can navigate, introspect, and mutate these OCI artifacts with a tool independent from a container runtime. -date: 2022-04-18 +description: OCI has long been the way we package container images. However the standard is being used to host more and more assets. Examples include Helm charts and other configuration. In this post, I'll explore how I navigate, introspect, and mutate OCI artifacts with tooling independent from container runtimes. +date: 2022-04-26 images: -- https://octetz.s3.us-east-2.amazonaws.com/yt_book_graphic.png -aliases: -- /latest +- https://octetz.s3.us-east-2.amazonaws.com/working-with-oci/working-with-oci-title-card.png --- -# Navigating OCI Artifacts +# Navigating Remote OCI Artifacts -OCI has long been the way we package container images. However, we've found this -standard to host more and more assets over time. Examples include Helm charts -and other configuration. In this post, I'll explore how we can navigate, -introspect, and mutate these OCI artifacts with a tools you can use independent -from a container runtime. +OCI has long been the way we package container images. Over time the standard +has being used to host more and more non container-image assets. Examples +include Helm charts and Carvel packages. In this post, I'll explore how I +navigate, introspect, and copy OCI artifacts with tooling independent from a +container runtime. ## Discovering, Introspecting, and Mutating -For discovering and introspecting artifacts, I use `crane`. It is both a command -line tool and excellent (Go) library from interacting with OCI artifacts in -repositories. I use this tool weekly as my Swiss Army Knife for OCI-related -operations. Since `crane` is independent of a container runtime, I find it -especially helpful in troubleshooting the assets used by container runtime, like -Docker. - -Let's start with a use-case where you'd like to understand which repos are -available in a registry. This can be fairly complex as projects like Harbor -support the idea of embedding projects into projects. For example, all of these -are valid repositories. - -* /project-a -* /project-a/asset-b -* /project-a/asset-c/ - -To start off, lets take a look at VMware's public-facing Harbor instance: - -```sh -$ crane catalog projects.registry.vmware.com/tce -TODO(joshrosso): output here -``` +For discovering and introspecting artifacts, I use +[crane](https://github.com/google/go-containerregistry/tree/main/cmd/crane). It +is them command line tool companion to the excellent (Go) library +[google/go-containerregistry](https://github.com/google/go-containerregistry). I +use `crane` as my Swiss Army Knife for OCI-related operations. Since +it is independent from a container runtime, I find it especially helpful in +troubleshooting the assets used by runtimes like `containerd`. If you know which repository/project you want to interact with, another common struggle is figuring out which tags are available. Most UIs like DockerHub or @@ -86,7 +69,7 @@ $ crane ls index.docker.io/nginx > Results above trimmed for brevity. -Now that you've got a list of tags, it would be cool to understand what +Now that you've got a list of tags, it would be good to understand what underlies a tag. Perhaps you don't yet wish to pull down all the contents, but more so want to look at the image's manifest to better understand its contents. @@ -181,9 +164,9 @@ The resulting JSON is: ``` In the above manifest list, we can see this image supports multiple -architectures! So we'll know that if a container runtime pulls down this tag, -it'll pull down one of the manifests in the list based on its architecture. For -example, we'd expect `containerd` running on an ARM Linux host to pull down +architectures! We now know if a container runtime pulls down this tag, it'll use +the manifest in the list related to its architecture. For example, we'd expect +`containerd` running on an ARM Linux host to pull down `index.docker.io/nginx@sha256:3100debc8e667aba0a8284f8cff4b209c941a061f2fb07ea8ab97a96c6caec17`. Now it is time to pull down an actual image. Container images are essentially @@ -307,7 +290,7 @@ var/ ``` Copying images between repositories is another useful action. For example, -consider the need to "relocate" an image into a registry you're hosting. A cool +consider the need to "relocate" an image into a registry you are hosting. A cool capability of `crane` is its ability to copy all architectures of an image and maintain all the digest values in the manifest. @@ -388,114 +371,10 @@ identical. $ COPY=$(crane manifest index.docker.io/joshrosso/nginx) \ ORIG=$(crane manifest index.docker.io/nginx) \ diff <(echo ${COPY}) <(echo ${ORIG}) - ``` -## Packaging and Pushing Non-Container Images - -When building container images, I tend to use `docker` as [its buildx -functionality works well for multi-architecture -builds](https://docs.docker.com/desktop/multi-arch/). However, I also push -non-container images up as OCI images/bundles. To do this, I've grown fond of -using `imgpkg`. - -> At the time of writing, I'm employed by VMware, the primary company sponsoring -> imgpkg. Independent of that, I do really like this tool. - -Let's start off producing an arbitrary manifest. In this case, we'll make a -simple Kubernetes manifest. - -```sh -cat < manifest.yaml -apiVersion: v1 -kind: Pod -metadata: - name: busybox-sleep -spec: - containers: - - name: busybox - image: busybox:1.28 - args: - - sleep - - "1000000" -EOF -``` - -We can push this manifest up as a OCI image using `imgpkg`: - -``` -$ imgpkg push -f manifest.yaml -i index.docker.io/joshrosso/busy-manifest:v1.0.0 -file: manifest.yaml -Pushed 'index.docker.io/joshrosso/busy-manifest@sha256:f17d2a9e70dfcf3f3c3ab1fc4129eddbefff930eec1e39642e5ebb8af25e9f2b' -Succeeded -``` - -This can be pulled and unpacked using tools like `crane` and `imgpkg`. In this -model, we're starting to treat OCI-compatible repositories as file stores. The -carvel tools take the model a step further by attempting to do nuanced things -with these manifests. For example we can "lock" the container image specified in -the manifest using `kbld`. - -```sh -$ kbld -f manifest.yaml --lock-output .imgpkg/images.yml -resolve | final: busybox:1.28 -> index.docker.io/library/busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47 ---- -apiVersion: v1 -kind: Pod -metadata: - annotations: - kbld.k14s.io/images: | - - origins: - - resolved: - tag: "1.28" - url: busybox:1.28 - url: index.docker.io/library/busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47 - name: busybox-sleep -spec: - containers: - - args: - - sleep - - "1000000" - image: index.docker.io/library/busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47 - name: busybox - -Succeeded -``` - -> `.imgpkg` directory needs to be created prior to running the above. - -In the above, you can see `index.docker.io/busybox:1.28` resolved to -`index.docker.io/library/busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47`. -This is referencing the digest value (SHA) rather than a tag. Since tags can be -overwritten, it'll provide better guarantees around the integrity of the -container image. I'll now re-push this image. - -``` -$ imgpkg push -f . -b index.docker.io/joshrosso/busy-manifest:v1.0.1 -dir: . -dir: .imgpkg -file: .imgpkg/images.yml -file: manifest.yaml -Pushed 'index.docker.io/joshrosso/busy-manifest@sha256:ee44383618580abc6126422dea5a5ecfed6f940cf8b43bb01bbf9cebe09bdddf' -Succeeded -``` - -Some subtle differences in the push above include: - -1. `-b` is used instead of `-i` to say this should be a bundle, rather than - image (check the OCI spec for details on these). -1. I'm specifying the full directory, `.`, as the file (`-f`) so that the - `.imgpkg` contents are also pushed in the bundle. - -A final example of where `imgpkg` shines is its ability to **recursively** copy -all OCI assets to a new registry. Using - -TODO(joshrosos): do a full recursive copy ## Shoutouts * [Thanks to the go-containerregistry maintainers for making awesome libraries and tools like `crane` and `ko`. Ya'll rock.](TODO(https://github.com/google/go-containerregistry/graphs/contributors) - -* [Thanks to the Carvel maintainers for continuing to push to envelope in how we - think about OCI artifacts and the packaging of them](TODO(https://github.com/vmware-tanzu/carvel-imgpkg/graphs/contributors)