Add a test to ingest 200MiB of data across stdout/stderr. Useful to test
how fast we can ingest stdio. The timing will always be a tad off as it
times between start and wait returning, and there's quite a lot in the
way.. but it's a good enough metric.
Everytime we grab a vsock connection we dup the conn and close the
original, otherwise we'd need to carry around the vsock connection type
everywhere as it closes the fd in its destructor. We weren't checking
the return value of dup however, so if it did fail we'd have a useless
filehandle with an fd of -1.
Addresses apple/container#141, where containers don't receive filesystem
events on mounted volumes, preventing incremental rebuilds and other
file-watching features. This PR implements the guest-side components for
FSNotify. Host-side implementation in the container repo will complete
the pipeline.
Summary:
- Add gRPC protocol definitions for filesystem event notifications
- Implement guest-side event handler that generates Linux inotify events
- Add CLI testing tool (`cctl fsnotify`) and integration test
infrastructure
I didn't like how we expose the asyncstream via a public connections
field. We should have the type conform to AsyncSequence and then hide
the underlying stream. This also stops listening on the stdio ports
after we get the initial connection.
Optimize unpack a little by trying to reduce allocations in the hot
path. Today for every file we read the entire file into memory and then
pass the data blob to the ext4 writer to eventually be written to the
sparse file. Before being written to the sparse file the data is copied
*again* to a temp buffer before finally hitting write(2) in FileHandle.
This change moves things around such that we can pass an optional buffer
to the ext4 create() (so we can reuse a buffer for file writes), as well
as stops reading entire files into memory by passing the archive entry
itself (wrapped in a ReaderStream object albeit) down to the writer.
Testing with unpacking every platform for
`docker.io/jenkins/jenkins:lts` on an M1 Max:
Old Avg (5 runs): 7.43s
New Avg (5 runs): 5.31s
This was a funny oversight. We have two port spaces, one for listening
sockets on the host, and one for vsock proxies in the guest, but only
the host "allocator" was being used. This didn't really matter as the
ports in the guest would still be unique, but would still be good to
fix.
The `mediaType` field in the `Index` struct was defined as a required
field, but according to the [OCI Image Index
Specification](https://github.com/opencontainers/image-spec/blob/main/image-index.md),
this field is optional.
This caused failures when loading OCI archives where the `index.json`
omits the top-level `mediaType` field, which is valid per the spec.
Tools like skopeo can generate such archives.
## Error before fix
```
keyNotFound(CodingKeys(stringValue: "mediaType", intValue: nil))
```
## Changes
- Changed `Index.mediaType` from `String` to `String?`
- Updated initializer to accept optional `mediaType` parameter
- Added comment documenting that field is optional per OCI spec
## Testing
Verified that OCI archives without a top-level `mediaType` field in
`index.json` now load successfully.
Fixes https://github.com/apple/container/issues/330
The mount flags are defined as `Int`s for musl but as `Int32`s for
glibc, despite the mount methods expecting `Int32` for both. This was
leading to build errors when building with glibc like:
```
| "async": .init(true, MS_SYNCHRONOUS),
| `- error: cannot convert value of type 'Int' to expected type 'Int32'
```
This PR allows for different Int sizes based on if we're building for
glibc or not to avoid this build error.
Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
This class does not work for glibc due to missing flags and methods.
This PR removes the ability to load the `Epoll` class when using glibc
for now.
Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
Many fields on the various OCI types use "omitempty" for encoding and
decoding the json representation in golang. This PR adds custom json
decoder functions to allow for behavior similar to "omitempty".
---------
Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
- Current implementation shuts down everything as soon as EOF is
detected on DispatchSourceRead for the relay. This is problematic for,
say an HTTP request where the client makes a request, and calls
`shutdown(fd, SHUT_WR)` to close the send side, but it expects to be
able to keep calling `recv()` to get the response.
- Changed cancel handlers so that the one that sees that both sources
have been cancelled then closes both the UDS and vsock fds.
- Updated vminitd VsockProxy to also do `shutdown(fd, SHUT_WR)` for read
hangup or EOF, and only close the underlying fds and unwire pollers on
full hangup, broken pipe, error, or when both sides half-close.
Closes#319
Introduces a new type capable of running > 1 container in the guest. The
API mostly follows LinuxContainer, and each individual container can be
addressed via any of the methods that require you to pass the
containerID as the first param. Today there's no support for namespace
sharing, but that shouldn't be terrible to support.
Closes#227
Previously, the bootlog was supplied once in the constructor to
VZVirtualMachineManager which meant that if you used this same manager
for multiple ctrs that all logs would end up going to the same file,
which becomes quite cumbersome to follow..
This change moves bootlog to be a container configuration param and also
moves it to be a VMConfiguration param, so it can be threaded through
from LinuxContainer -> vmm.create() and be truly container unique now.
The largest driver for this was the integration tests which today every
single test spits out logs to a singular file, making guest
investigations tricky to actually look into.
Result after:
```
➜ containerization git:(bootlog-per-ctr) ✗ ls -alh bin/bootlogs
total 1520
drwxr-xr-x@ 24 dcantah staff 768B Oct 22 17:34 .
drwxr-xr-x@ 8 dcantah staff 256B Oct 22 17:34 ..
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-cat-mount.log
-rw-------@ 1 dcantah staff 22K Oct 22 17:34 test-cgroup-limits.log
-rw-------@ 1 dcantah staff 249K Oct 22 17:34 test-concurrent-processes-output-stress.log
-rw-------@ 1 dcantah staff 167K Oct 22 17:34 test-concurrent-processes.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-devconsole.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-hostname.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-hosts-file.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-manager.log
-rw-------@ 1 dcantah staff 22K Oct 22 17:34 test-container-reuse.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-statistics.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-stdin.log
-rw-------@ 1 dcantah staff 0B Oct 22 17:34 test-nested-virt.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-pause-resume-io.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-pause-resume-wait.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-pause-resume.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-custom-home-envvar.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-echo-hi.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-false.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-home-envvar.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-true.log
-rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-tty-envvar.log
-rw-------@ 1 dcantah staff 38K Oct 22 17:34 test-process-user.log
```
Today this protocols create method is just odd. Our only implementation
of it immediately casts to LinuxContainer, failing if it cannot do so. I
think what might make more sense is to pass in a configuration itself
with core parameters that we expect every vmm to be able to support, and
then in a specific implementation they can continue to cast this type to
a specific one to possibly extract some extra configuration values
(rosetta for VZ for example).
This rework will also make it simpler to support a Pod type, as the vm
setup is identical and simple.
As a first step to making a pod type, move the nested process
configuration out of LinuxContainer.Configuration.Process and into a
standalone LinuxProcessConfiguration type.
The `vmm.create` call is already made within an async function. This PR
updates the protocol to allow other vmm implementations more flexibility
in `create`.
Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
Make generic mounts more flexible by passing in runtime options and
making the init function public
Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
I only see a very small delta halving this (.6 seconds), and the default
will likely stress the CI machines a lot more. This change also swaps
the atomic increments to .relaxed, as there's no ordering required, just
need no torn writes.
Fixes an issue first described in
https://github.com/apple/container/issues/740.
Previously the initial process of the container was using vminitd's
environment variables to find the target executable. This PR updates the
code to use the container's configured environment for the initial
process instead. The behavior of an exec in a container should be the
same as before.
Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
This change moves LinuxContainer to use the new AsyncMutex type. This allows us to keep using our old state machine model, but lock around the entirety of operations. This also allows us to axe quite a few states that were intermediary states only there to protect against multiple threads calling (even if it was unlikely..) the same method at the same time (.creating, .starting, .stopping etc.).
The rationale is: Mutex is somewhat tricky to use as you can't (for good reason) do any async work inside the closure. This makes some of our work difficult as every single interaction with the guest is via an rpc, which is all async. There really isn't a worry about contention, because most container methods will only ever be invoked once (create, start, stop) so a big heavy lock isn't much of a deterrent.
Fixes#320
Today we don't actually setup any cgroup limits, as because there's a
1-1 mapping from container<->vm we can just use the VMs resources as the
limit (can't use more than 1GB if that's all the guest sees :) ).
However, if we ever supported > 1 container in the guest it'd be
necessary to actually setup the cg limits. This change just sets a
memory limit and cpu toggles to match whatever was specific for the
container.
We already have `AsyncLock`, but in some cases it'd be nice to have the
type protect a piece of data that you access through the lock, much like
the Synchronization frameworks new `Mutex` type.
This change adds such a type.
- Closes#255.
- Fixes ProxyUtils so that the environment variable to be used for proxy
selection is determined by the request scheme.
- RegistryClient uses ProxyUtils to get the proxy URL used by the
HTTPClient.
- Tweak hostname resolution error message to avoid misleading output if
the proxy hostname cannot be resolved.
Closes#138
This rounds up cgroup and network stats (via netlink) and exposes them
via an agent rpc and on LinuxContainer. While this is not an entirely
accurate view into the full resources being used by the container given
the virtualized nature (doesn't account for vcpu, device etc overhead)
it should give an accurate overview of the workload resource usage in
the guest.
This change besides the stated goal also removes the interfaceStatistics
rpc and just moves these network stats into the new containerStatistics
one. Related to that, it also renames the InterfaceStatistics struct to
NetworkStatistics and moves this into ContainerStatistics as a nested
struct.
Adds support for grabbing stats for an iface. This will be surfaced in a
coming change to get container level statistics from the guest, where
one field will be network stats.
- Motivation is to be able to add the `swift-backtrace-static` binary as
needed.
- Use singlular `--add-file` and `--label` options since both accept
multiple invocations with single values each.
Example usage:
```bash
./bin/cctl rootfs create \
--vminitd vminitd/bin/vminitd \
--vmexec vminitd/bin/vmexec \
--add-file /Users/john/.swiftpm/swift-sdks/swift-6.2-RELEASE_static-linux-0.0.1.artifactbundle/swift-6.2-RELEASE_static-linux-0.0.1/swift-linux-musl/musl-1.2.5.sdk/aarch64/usr/libexec/swift/linux-static/swift-backtrace-static:sbin/swift-backtrace \
--label org.opencontainers.image.source=https://github.com/apple/containerization \
--image vminit:latest \
bin/init.rootfs.tar.gz
```
Have wait return a struct containing the exit code and the time at which
the process actually exited. This is useful so higher level tools (like
container) don't need to craft a date themselves, and it will be more
accurate as it's taken in the guest directly after the process exits.
swift-nio's public export of NIOFileSystem was removed in 2.86.1:
https://github.com/apple/swift-nio/pull/3370
NIOFileSystem was not yet supposed to be public, but _NIOFileSystem
depended on it as a public import. This made it possible for
`containerization` to see the `NIOFileSystem` package by accident.
Replacing the use of `NIOFileSystem` by `_NIOFileSystem`, as used
elsewhere, fixes the problem.
## Why does CI currently pass?
The change in `swift-nio` does not currently cause `containerization`'s
CI to fail because `Package.resolved` pins `swift-nio` to 2.83.0, before
the change was made. New versions of upstream dependencies will not be
tested until `Package.resolved` is explicitly updated.
When containerization is built as a dependency of a end-user project,
its `Package.resolved` file is ignored. Instead, the dependency
constraints from containerization's Package.swift file are combined with
those of the project and any other library dependencies, so SwiftPM or
Xcode can find a set of mutually compatible packages. This can lead to
new versions of containerization's upstream dependencies being used,
even though those versions have never been tested in CI.
The build failure can be demonstrated by creating a new package which
depends on `containerization` but does not constrain package versions:
```
% swift package init --type executable
Creating executable package: test
Creating Package.swift
Creating Sources
Creating Sources/test/test.swift
% cat > Package.swift <<EOF
heredoc> // swift-tools-version: 6.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "test",
platforms: [
.macOS(.v26),
],
dependencies: [
.package(url: "https://github.com/apple/containerization", from: "0.7.2"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "test",
dependencies: [
.product(name: "Containerization", package: "containerization"),
]
),
]
)
EOF
% swift build
...
/private/tmp/test/.build/checkouts/containerization/Sources/ContainerizationOCI/Client/RegistryClient+Fetch.swift:25:8: error: no such module 'NIOFileSystem'
23 |
24 | #if os(macOS)
25 | import NIOFileSystem
| `- error: no such module 'NIOFileSystem'
26 | #endif
27 |
```