Skip to content

Instantly share code, notes, and snippets.

@skyzyx
Last active November 1, 2023 16:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skyzyx/d82b7d9ba05523dd1a9301fd282b32c4 to your computer and use it in GitHub Desktop.
Save skyzyx/d82b7d9ba05523dd1a9301fd282b32c4 to your computer and use it in GitHub Desktop.
Alternate CPU Architecture Identifiers

Alternate CPU Architecture Identifiers

I work with Linux in Docker on the daily. And the people and machines I support have a blend of Intel/AMD and ARM/Graviton/AppleSilicon chips.

And the thing that bites me regularly is that different operating systems return different values for uname -m, even when they're the same thing.

The Problem this Helps With

  • We have users on macOS, Windows, and Linux.
  • We have a blend of worker laptops using both Intel/AMD and Apple Silicon CPU architectures.
  • We have cloud servers in AWS, GCP, and Azure.
  • We have a blend of cloud servers using both Intel/AMD and Apple Silicon CPU architectures.
  • We rely heavily on Docker/Terraform for consistency/repeatability, and to better scale the perpetually-limited resources of our DevOps/SRE/Cloud/Platform engineering teams.
  • Docker runs natively on Linux.
  • Docker runs virtualized in macOS and Windows.
  • Software running inside the Linux-based Docker containers is most efficient when compiled for the current CPU architecture.
  • Out on the internet, people build packages that can be installed. Many are not inside the Linux system’s package manager, and must be installed from the web. The people who publish these packages use a variety of identifiers for Intel-compatible vs ARM-compatible CPU architectures. There is no consistency.

When building tooling/solutions for a heterogenous set of machines across an enterprise, you need to solve for (at least) the following matrix.

  • Current OS
  • Current CPU architecture
  • Package filenames on the internet

Deploying software as Docker containers (running Linux) helps normalize things like:

  • Relying on GNU vs BSD-flavored CLI tools
  • Download packages into the Docker container, worrying only about Linux
  • Deploying software across worker laptops running different host operating systems
  • Deploying software to Linux servers in the cloud

But these solutions don't solve the (relatively new) problem of an uptick of 64-bit ARM software/CPUs being added to the matrix — and the fact that these are not referred-to in a unified, consistent way.

Common values for uname -m

OS 64-bit Intel-compat 64-bit ARM
macOS x86_64 arm64
Red Hat family¹ x86_64 aarch64
Debian family² amd64 arm64
Busybox family³ x86_64 aarch64
Windows WSL2⁴ Varies Varies
  • ¹ Red Hat family includes Red Hat Enterprise Linux, CentOS, Fedora, Amazon Linux, and others.
  • ² Debian family includes Debian, Ubuntu, Linux Mint, and others.
  • ³ Busybox family includes Busybox, Alpine Linux, and others.
  • ⁴ Windows WSL2 returns whatever the underlying Linux installation says.

(Extremely) Brief Overview

There are different names for (essentially) the same CPU architectures. Different vendors use different names for the same thing.

Here's an (extremely) brief overview of modern CPU architectures that you most commonly find in cloud service providers and modern desktops/laptops.

This is meant to be illustrative, not comprehensive. As of today, these are the top 2 by a large margin.

Family Arch IDs Description
x86 x86_64, amd64, x64 Intel’s 80x86 line of CPUs, and AMD clones. Shortened to x86 (or sometimes x64), these are the newer 64-bit models. Includes Amazon EC2 instances powered by Intel Xeon™ or AMD EPYC™ CPUs, and Intel i-Series Macs.
arm arm64, arm64v8, arm64v9, aarch64 ARM v8/v9, 64-bit. AWS Graviton, Apple A7 and newer (including M-series). All 64-bit ARM chips are ARM v8/v9, but the inverse is not true. arm64 == ( arm64v8 || arm64v9 ). Includes Amazon EC2 instances powered by AWS Graviton™ CPUs and Apple M-Series Macs.

Example

arch.py $(uname -m) --x86 x86_64 --arm aarch64
arch.py $(uname -m) --x86 amd64 --arm arm64
arch.py $(uname -m) --x86 x86_64 --arm arm64
  1. Docker Compose uses x86_64 and aarch64 in the filenames of their releases.
  2. Docker Compose is written in Go, which compiles (easily) for all Linuxes (e.g., RHEL, Debian, Alpine).
  3. This will determine the correct URL to construct based on the value of uname -m for the present system, at runtime.
wget -q "https://github.com/docker/compose/releases/download/v${COMPOSE_VERSION}/docker-compose-linux-$(./arch.py $(uname -m) --x86 x86_64 --arm aarch64)" \
    -O /usr/local/lib/docker/cli-plugins/docker-compose

chmod +x /usr/local/lib/docker/cli-plugins/docker-compose

I use these exact lines in my Dockerfiles that I write for multi-architecture Docker image builds. This script is extremely lo-fi, but it is composable and has saved me dozens of hours over the last couple of years.

#! /usr/bin/env python3
import argparse
import sys
MIN_PYTHON = (3, 8)
if sys.version_info < MIN_PYTHON:
sys.exit(
"This script requires Python {version} or newer.".format(
version='.'.join([str(n) for n in MIN_PYTHON])
)
)
#-------------------------------------------------------------------------------
# arch.py $(uname -m) --x86 amd64 --arm arm64
def main():
parser = argparse.ArgumentParser(
description="Return alternate CPU architecture identifiers. Only supports 64-bit Intel and ARM CPUs under Darwin/Linux.",
)
parser.add_argument("arch", help="The arch value from `uname -m`.")
parser.add_argument(
"--x86",
help="If the arch value matches `x86_64`, return this value instead. Common choices are `amd64`."
)
parser.add_argument(
"--arm",
help="If the arch value matches `arm64`, return this value instead. Common choices are `aarch64`."
)
flags = parser.parse_args()
if flags.arch is None:
print("Pass the arch value from `uname -m`.")
sys.exit(1)
if (flags.arch == "x86_64" or flags.arch == "amd64") and flags.x86 is not None:
print(flags.x86)
elif (flags.arch == "arm64" or flags.arch == "aarch64") and flags.arm is not None:
print(flags.arm)
else:
print(flags.arch)
# -----------------------------------------------------------------------------
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment