moby: manifest list (multiarch) picks wrong arch on ARMv6

Description

The command docker pull executed on a armv6 Raspberry Pi One (“Model B Plus Rev 1.2”) running a 32-bit Raspbian OS will fail to pull the correct docker image defined in a manifest as soon as a ‘linux/arm/v7’ manifest entry exists.

Steps to reproduce the issue:

  1. uname -m && docker run --rm arm32v6/alpine:3.8 sh -c 'cat /etc/*release | grep PRETTY_NAME'
  2. uname -m && docker run --rm arm32v6/alpine:3.9 sh -c 'cat /etc/*release | grep PRETTY_NAME'
  3. uname -m && docker run --rm alpine:3.8 sh -c 'cat /etc/*release | grep PRETTY_NAME'
  4. uname -m && docker run --rm alpine:3.9 sh -c 'cat /etc/*release | grep PRETTY_NAME'

Describe the results you received:

$ uname -m && docker run --rm arm32v6/alpine:3.8 sh -c 'cat /etc/*release | grep PRETTY_NAME'
armv6l
PRETTY_NAME="Alpine Linux v3.8"
$ uname -m && docker run --rm arm32v6/alpine:3.9 sh -c 'cat /etc/*release | grep PRETTY_NAME'
armv6l
PRETTY_NAME="Alpine Linux v3.9"
$ uname -m && docker run --rm alpine:3.8 sh -c 'cat /etc/*release | grep PRETTY_NAME'
armv6l
PRETTY_NAME="Alpine Linux v3.8"
$ uname -m && docker run --rm alpine:3.9 sh -c 'cat /etc/*release | grep PRETTY_NAME'
armv6l
$ echo $?
139

For steps 1,2 and 3, I was expecting these results. However, at step 4, the ‘docker run’ command did not produce any output and exits with a 139 return code.

Describe the results you expected:

For step 4, I was expecting the same result as in step 2:

armv6l
PRETTY_NAME="Alpine Linux v3.9"

Additional information you deem important (e.g. issue happens only occasionally):

There is no “linux/arm/v7” image on alpine:3.8 manifest:

$ docker run --rm mplatform/mquery alpine:3.8Image: alpine:3.8
 * Manifest List: Yes
 * Supported platforms:
   - linux/amd64
   - linux/arm/v6
   - linux/arm64
   - linux/386
   - linux/ppc64le
   - linux/s390x

But alpine:3.9 manifest defines a linux/arm/v7 entry:

$ docker run --rm mplatform/mquery alpine:3.9
Image: alpine:3.9
 * Manifest List: Yes
 * Supported platforms:
   - linux/amd64
   - linux/arm/v6
   - linux/arm/v7
   - linux/arm64
   - linux/386
   - linux/ppc64le
   - linux/s390x

If you prefer, with docker manifest inspect:

$ DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect alpine:3.8
[...]
"digest": "sha256:dabea2944dcc2b86482b4f0b0fb62da80e0673e900c46c0e03b45919881a5d84",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v6"
         }
[...]

$ DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect alpine:3.9
[...]
{
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:7a3d88cbc7e2d6c0213deaf2d006933c9f5905c4eb7846b703a66fc6504000b7",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v6"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:cfd8b55d209956f63c8fcc931f5c6874984e5e0ffdcb8f45ba9085f190385d73",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v7"
         }
      },
[...]

An evidence that dockerd 19.03.8 pulls the (wrong) linux/arm/v7 images variant:

$ docker pull arm32v7/alpine:3.9
$ docker images | grep "alpine"
arm32v6/alpine        3.8                                                   e60f4bcd6c89        4 months ago        4.01MB    # OK
alpine                3.8                                                   e60f4bcd6c89        4 months ago        4.01MB    # OK
arm32v6/alpine        3.9                                                   4fe9c9b2a0c4        4 weeks ago         4.68MB    # OK
arm32v7/alpine        3.9                                                   9df0ff5446fc        4 weeks ago         3.72MB    # OK
alpine                3.9                                                   9df0ff5446fc        4 weeks ago         3.72MB    # NOK: should be '4fe9c9b2a0c4' and not '9df0ff5446fc' ! 

Output of docker version:

Client: Docker Engine - Community
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.12.17
 Git commit:        afacb8b
 Built:             Wed Mar 11 01:35:24 2020
 OS/Arch:           linux/arm
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.8
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.17
  Git commit:       afacb8b
  Built:            Wed Mar 11 01:29:22 2020
  OS/Arch:          linux/arm
  Experimental:     false
 containerd:
  Version:          1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Output of docker info:

Client:
 Debug Mode: false

Server:
 Containers: 2
  Running: 2
  Paused: 0
  Stopped: 0
 Images: 34
 Server Version: 19.03.8
 Storage Driver: overlay2
  Backing Filesystem: <unknown>
  Supports d_type: true
  Native Overlay Diff: true
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: active
  NodeID: 2zsjxcgeymlaecu00y8j0i1uw
  Is Manager: true
  ClusterID: t5ftctfw0dlnxbve8ilm7yo0x
  Managers: 1
  Nodes: 1
  Default Address Pool: 10.0.0.0/8  
  SubnetSize: 24
  Data Path Port: 4789
  Orchestration:
   Task History Retention Limit: 5
  Raft:
   Snapshot Interval: 10000
   Number of Old Snapshots to Retain: 0
   Heartbeat Tick: 1
   Election Tick: 10
  Dispatcher:
   Heartbeat Period: 5 seconds
  CA Configuration:
   Expiry Duration: 3 months
   Force Rotate: 0
  Autolock Managers: false
  Root Rotation In Progress: false
  Node Address: 192.168.0.114
  Manager Addresses:
   192.168.0.114:2377
 Runtimes: runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc version: dc9208a3303feef5b3839f4323d9beb36df0a9dd
 init version: fec3683
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 4.19.97+
 Operating System: Raspbian GNU/Linux 10 (buster)
 OSType: linux
 Architecture: armv6l
 CPUs: 1
 Total Memory: 432.4MiB
 Name: white
 ID: QDNQ:Y3WR:ALAX:BXDG:JRKM:VBXW:SEC3:LX7D:LLFH:GG2O:EHK6:GG4G
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

WARNING: No swap limit support
WARNING: No cpu cfs quota support
WARNING: No cpu cfs period support
WARNING: No cpuset support

Additional environment details (AWS, VirtualBox, physical, etc.):

This test was ran on a “Raspberry Pi Model B Plus Rev 1.2”. Important device info: it is an armhf v6 (and not v7 !) device, even if its ‘CPU architecture’ is set to 7 !

$ dpkg --print-architecture
armhf
$ uname -m
armv6l
$ cat /proc/cpuinfo | grep "CPU architecture"
CPU architecture: 7

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 17 (7 by maintainers)

Most upvoted comments

My 2 cents analysis:

  1. I know that the ARM world is very complex (see https://en.wikipedia.org/wiki/List_of_ARM_microarchitectures and/or https://en.wikipedia.org/wiki/ARM_architecture), and I know it is much much more complex than only ‘armv32v5, arm32v6, arm32v7 or arm64v8’.
  2. For debian, armhf is armv7 + hard floating-point (see https://wiki.debian.org/ArmHardFloatPort#Port_naming_debate_notes). But that’s not the case for Raspbian !
    Raspbian builds a single image for all of the Raspberry families, so you will get an armhf 32-bit, hard floating-point system, but built for the ARMv6 ISA (with VFP2), unlike Debian's ARMv7 ISA (with VFP3) port.
    
    See https://wiki.debian.org/RaspberryPi#Debian_and_Raspbian.
  3. On a “Raspberry Pi Model B Plus Rev 1.2” (as reported by cat /proc/device-tree/model), the lscpu command and the uname -m (or arch) clearly understand it is an arm v6 device, while dpkg --print-architecture confirm it is a hard floating-point system:
    $ lscpu -J | grep Architecture
         {"field":"Architecture:", "data":"armv6l"},
    $ uname -m
    armv6l
    $ arch
    armv6l
    $ `dpkg --print-architecture`
    armhf
    
  4. This arm32v6 (armv6l) device as a ‘CPU Architecture’ set by the kernel to 7 !
    $ cat /proc/cpuinfo 
    processor       : 0
    model name      : ARMv6-compatible processor rev 7 (v6l)
    BogoMIPS        : 697.95
    Features        : half thumb fastmult vfp edsp java tls 
    CPU implementer : 0x41
    CPU architecture: 7
    CPU variant     : 0x0
    CPU part        : 0xb76
    CPU revision    : 7
    
    Hardware        : BCM2835
    Revision        : 0010
    Serial          : 00000000c0c1d288
    Model           : Raspberry Pi Model B Plus Rev 1.2
    
  5. By the way, this produce some discussions already in 2012 (see https://www.raspberrypi.org/forums/viewtopic.php?t=12614). Apparently, CPU architecture is set to 7 when the cpu support “VMSAv7 or PMSAv7”, but it is not related to ‘ARM v6 or v7’.
  6. Notes that dpkg --print-architecture returns armhf for this device
  7. Containerd use the “CPU architecture” field of the ‘/proc/cpuinfo’ pseudo file to set the arm device ‘variant’: See https://github.com/containerd/containerd/blob/release/1.3/platforms/cpuinfo.go#L93. In our case, it is a mistake, because containerd will consider this RPI1 device as a ‘arm32v7’ device, while it is not true !
  8. By the way, https://github.com/containerd/containerd/blob/release/1.3/platforms/database.go#L98 consider that ‘armhf’ is an equivalent to ‘arm32v7’, which is also not always true neither (it is at least not true for this ‘Raspberry Pi Model B Plus Rev 1.2’ running the Raspian OS)
  9. Moby (and therefore Docker), embeds the containerd code and use it to determine the local server plateform: see https://github.com/moby/moby/blob/19.03/distribution/pull_v2_unix.go#L21.
  10. When ‘docker pull’ needs to find the correct docker image according to the manifest, it uses this containerd code to find the platforms.DefaultSpec() which will in our case return ‘v7’.
  11. Consequence: as soon as a ‘arm32v7’ image will be declared in the manifest, the filterManifests method will be happy to find a perfect match that respects the OCI specs, and that’s the image that will be downloaded. And in that case, it will fail as the device is not armv7 !
  12. However, if there is not ‘v7’ variant in the manifest, the ‘fallback’ mode will be trigger, and all the ‘arm’ manifest entry will be added to the ‘matches’ list (which is by the way CURRENTLY a little bit useless as the ‘best matching’ algorithm will simply return the first found variant (see https://github.com/moby/moby/blob/19.03/distribution/pull_v2.go#L792) so today, stopping on the first match could be enough. But’s that not what I recommend, because the full list will be needed to fix the issue Issue 37647).

I am a little over my head hear, but I think I have a simliar/related issue while running a Docker build on an rpi B (arm32v6) model.

Sending build context to Docker daemon  10.39MB
Step 1/11 : FROM arm32v6/node:14.15.4-alpine AS dependencies
14.15.4-alpine: Pulling from arm32v6/node
Digest: sha256:20cc61f7f6f2ec5754071c3ef2db622d29a785f66a5acc66a108f15b34621679
Status: Downloaded newer image for arm32v6/node:14.15.4-alpine
 ...
Step 5/11 : RUN npm install
 ---> [Warning] The requested image's platform (linux/arm) does not match the detected host platform (linux/arm/v6) and no specific platform was requested
 ---> Running in d97792defda3

Does anyone have any suggestions as for how to resolve this? Am I missing something? npm install has been running since forever…

Thanks!