Container internals

Building applications for Kubernetes and OpenShift requires an understanding of containers, which are small images that are preassembled and deployed to a host for execution. This series of lessons gives a basic understanding on containers, useful for application developers.

Download Podman Access the Developer Sandbox

 

This lesson is focused on understanding how container images are built, tagged, organized, and leveraged to deliver software in a range of use cases.

In order to get full benefit from taking this lesson, you need:

In this lesson, you will:

  • Internalize the difference between base images and multi-layered images.
  • Understand the full URL of an image/repository.
  • Command a complete understanding of what's inside a container image.

Image layers and repositories: Inspect base images, layers, and histories

The goal of this lesson is to understand the difference between base images and multi-layered images (repositories). Also, we'll try to understand the difference between an image layer and a repository.

  1. Clone the GitHub repository and move it into the resulting directory, using the following two commands:

    git clone https://github.com/redhat-developer-demos/intro-both
    cd intro-both
  2. Let's take a look at some base images. We will use the podman history command to inspect all of the layers in these repositories. Notice that these container images have no parent layers. These are base images, and they are designed to be built upon. First, let's look at the full ubi9 base image by running the following command:

    podman pull registry.access.redhat.com/ubi9:9.6-1747219013
    podman history registry.access.redhat.com/ubi9:9.6-1747219013

    By the way, you can find this image at the Red Hat Ecosystem Catalog.

  3. Now, let's take a look at the minimal base image, which is part of the Red Hat Universal Base Image (UBI) collection. Notice that it's quite a bit smaller.

    podman pull registry.access.redhat.com/ubi9/ubi-minimal:latest
    podman history registry.access.redhat.com/ubi9/ubi-minimal:latest
  4. Use a simple Dockerfile we created for you to build a multi-layered image:

    podman build -t ubi9-change -f Dockerfile
  5. Now view the image by running the following command:

    podman images

    Do you see the newly-created ubi9-change tag? Can you see all of the layers that make up the new image/repository/tag? This command even shows a short summary of the commands run in each layer. This is very convenient for exploring how an image was made. 

  6. To see these layers, run the following command:

    podman history ubi9-change

Note

The first image ID (bottom) listed in the output matches the registry.access.redhat.com/ubi9/ubi image. Remember, it is important to build on a trusted base image from a trusted source (provenance or chain of custody). Container repositories are made up of layers, but we often refer to them simply as "container images" or containers. When architecting systems, we must be precise with our language or else we could cause confusion to our end users.

Image URLs: Map business requirements to the URL, namespace, repository, and tag

Now we are going to inspect the different parts of the URL that you pull. 

The most common command is similar to this command, where only the repository name is specified:

podman inspect ubi9/ubi

But what's really going on? Well, similar to DNS, the podman command line is resolving the full URL and tag of the repository on the registry server. The following command will give you the same results:

podman inspect registry.access.redhat.com/ubi9/ubi:latest

You can run any of the following commands and get the same results:

podman inspect registry.access.redhat.com/ubi9/ubi:latest
podman inspect registry.access.redhat.com/ubi9/ubi
podman inspect ubi9/ubi:latest
podman inspect ubi9/ubi

Let's build another image, but give it a tag other than latest:

podman build -t ubi9:test -f Dockerfile

Now there is another tag.

podman images

Try the resolution trick again. What happened?

podman inspect ubi9

It failed, but why? Try again with a complete URL:

podman inspect ubi9:test

Notice that Podman resolves container images, similar to DNS resolution. Each container engine is different, and Docker will actually resolve some things podman doesn't because there is no standard on how image URIs are resolved. If you test long enough, you will find many other caveats to namespace, repository, and tag resolution. 

Generally, it's best to always use the full URL, specifying the server, namespace, repository, and tag. Remember this when building scripts. Containers seem deceptively easy, but you need to pay attention to details.

Image internals: Inspect the libraries, interpreters, and operating system components in a container image

In this exercise, we will take a look at what's inside the container image. Java is particularly interesting because it uses glibc, even though most people don't realize it. We will use the ldd command to prove it, which shows you all of the libraries that a binary is linked against. When a binary is dynamically linked (libraries loaded when the binary starts), these libraries must be installed on the system or else the binary will not run. 

In this example in particular, you can see that getting a JVM to run with the same behavior requires compiling and linking in the same way. Stated another way, all Java images are not created equal:

podman run -it registry.access.redhat.com/jboss-eap-7/eap70-openshift ldd -v -r /usr/lib/jvm/java-1.8.0-openjdk/jre/bin/java

Notice that dynamic scripting languages are also compiled and linked against system libraries:

podman run -it registry.access.redhat.com/ubi9/ubi ldd /usr/bin/python3

Inspecting a common tool like curl demonstrates how many libraries you are using from the operating system. 

First, start the RHEL tools container. This is a special image that Red Hat releases with all of the tools necessary for troubleshooting in a containerized environment. It's rather large, but quite convenient.

podman run -it registry.access.redhat.com/ubi9/toolbox bash

Once inside the container, take a look at all of the libraries curl is linked against:

ldd /usr/bin/curl

Let's see what packages deliver those libraries. When there is a new CVE discovered, a new container image will need to be built to patch it:

rpm -qf /lib64/libssl.so.3

Exit the ubi9 toolbox container:

exit

It's a similar story with Apache and most other daemons and utilities that rely on libraries for security or deep hardware integration:

podman run -it registry.access.redhat.com/ubi9/httpd-24 bash  

Inspect the mod_ssl Apache module:

ldd /usr/lib64/httpd/modules/mod_ssl.so

Once again, we find a library provided by OpenSSL:

rpm -qf /usr/lib64/libcrypto.so.3

To exit the httpd24 container, run the following command:

exit

Summary

What does this all mean? Well, it means you need to be ready to rebuild all of your container images any time there is a security vulnerability in one of the libraries inside it.

It is a journey, and we are always happy to help. If you want more options, consider the following learning paths:

The next lesson in this series looks at container engines and the Linux kernel.

Previous resource
Containers 101
Next resource
Trust