mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-25 08:23:09 +00:00
virtualisation.oci-containers: Add new imageStream
option (#335430)
This adds a new `imageStream` option that can be used in conjunction with `pkgs.dockerTools.streamLayeredImage` so that the image archive never needs to be materialized in the `/nix/store`. This greatly improves the disk utilization for systems that use container images built using Nix because they only need to store image layers instead of the full image. Additionally, when deploying the new system and only new layers need to be built/copied.
This commit is contained in:
parent
51f0e92aeb
commit
0b6fa5ee40
@ -33,6 +33,25 @@ let
|
|||||||
example = literalExpression "pkgs.dockerTools.buildImage {...};";
|
example = literalExpression "pkgs.dockerTools.buildImage {...};";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
imageStream = mkOption {
|
||||||
|
type = with types; nullOr package;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Path to a script that streams the desired image on standard output.
|
||||||
|
|
||||||
|
This option is mainly intended for use with
|
||||||
|
`pkgs.dockerTools.streamLayeredImage` so that the intermediate
|
||||||
|
image archive does not need to be stored in the Nix store. For
|
||||||
|
larger images this optimization can significantly reduce Nix store
|
||||||
|
churn compared to using the `imageFile` option, because you don't
|
||||||
|
have to store a new copy of the image archive in the Nix store
|
||||||
|
every time you change the image. Instead, if you stream the image
|
||||||
|
then you only need to build and store the layers that differ from
|
||||||
|
the previous image.
|
||||||
|
'';
|
||||||
|
example = literalExpression "pkgs.dockerTools.streamLayeredImage {...};";
|
||||||
|
};
|
||||||
|
|
||||||
login = {
|
login = {
|
||||||
|
|
||||||
username = mkOption {
|
username = mkOption {
|
||||||
@ -275,6 +294,9 @@ let
|
|||||||
${optionalString (container.imageFile != null) ''
|
${optionalString (container.imageFile != null) ''
|
||||||
${cfg.backend} load -i ${container.imageFile}
|
${cfg.backend} load -i ${container.imageFile}
|
||||||
''}
|
''}
|
||||||
|
${optionalString (container.imageStream != null) ''
|
||||||
|
${container.imageStream} | ${cfg.backend} load
|
||||||
|
''}
|
||||||
${optionalString (cfg.backend == "podman") ''
|
${optionalString (cfg.backend == "podman") ''
|
||||||
rm -f /run/podman-${escapedName}.ctr-id
|
rm -f /run/podman-${escapedName}.ctr-id
|
||||||
''}
|
''}
|
||||||
@ -282,10 +304,10 @@ let
|
|||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
wantedBy = [] ++ optional (container.autoStart) "multi-user.target";
|
wantedBy = [] ++ optional (container.autoStart) "multi-user.target";
|
||||||
wants = lib.optional (container.imageFile == null) "network-online.target";
|
wants = lib.optional (container.imageFile == null && container.imageStream == null) "network-online.target";
|
||||||
after = lib.optionals (cfg.backend == "docker") [ "docker.service" "docker.socket" ]
|
after = lib.optionals (cfg.backend == "docker") [ "docker.service" "docker.socket" ]
|
||||||
# if imageFile is not set, the service needs the network to download the image from the registry
|
# if imageFile or imageStream is not set, the service needs the network to download the image from the registry
|
||||||
++ lib.optionals (container.imageFile == null) [ "network-online.target" ]
|
++ lib.optionals (container.imageFile == null && container.imageStream == null) [ "network-online.target" ]
|
||||||
++ dependsOn;
|
++ dependsOn;
|
||||||
requires = dependsOn;
|
requires = dependsOn;
|
||||||
environment = proxy_env;
|
environment = proxy_env;
|
||||||
@ -393,6 +415,17 @@ in {
|
|||||||
config = lib.mkIf (cfg.containers != {}) (lib.mkMerge [
|
config = lib.mkIf (cfg.containers != {}) (lib.mkMerge [
|
||||||
{
|
{
|
||||||
systemd.services = mapAttrs' (n: v: nameValuePair "${cfg.backend}-${n}" (mkService n v)) cfg.containers;
|
systemd.services = mapAttrs' (n: v: nameValuePair "${cfg.backend}-${n}" (mkService n v)) cfg.containers;
|
||||||
|
|
||||||
|
assertions =
|
||||||
|
let
|
||||||
|
toAssertion = _: { imageFile, imageStream, ... }:
|
||||||
|
{ assertion = imageFile == null || imageStream == null;
|
||||||
|
|
||||||
|
message = "You can only define one of imageFile and imageStream";
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
lib.mapAttrsToList toAssertion cfg.containers;
|
||||||
}
|
}
|
||||||
(lib.mkIf (cfg.backend == "podman") {
|
(lib.mkIf (cfg.backend == "podman") {
|
||||||
virtualisation.podman.enable = true;
|
virtualisation.podman.enable = true;
|
||||||
|
@ -18,7 +18,7 @@ let
|
|||||||
inherit backend;
|
inherit backend;
|
||||||
containers.nginx = {
|
containers.nginx = {
|
||||||
image = "nginx-container";
|
image = "nginx-container";
|
||||||
imageFile = pkgs.dockerTools.examples.nginx;
|
imageStream = pkgs.dockerTools.examples.nginxStream;
|
||||||
ports = [ "8181:80" ];
|
ports = [ "8181:80" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -20,7 +20,7 @@ let
|
|||||||
inherit backend;
|
inherit backend;
|
||||||
containers.nginx = {
|
containers.nginx = {
|
||||||
image = "nginx-container";
|
image = "nginx-container";
|
||||||
imageFile = pkgs.dockerTools.examples.nginx;
|
imageStream = pkgs.dockerTools.examples.nginxStream;
|
||||||
ports = ["8181:80"];
|
ports = ["8181:80"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -23,7 +23,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
|
|||||||
"traefik.http.routers.nginx.rule=Host(`nginx.traefik.test`)"
|
"traefik.http.routers.nginx.rule=Host(`nginx.traefik.test`)"
|
||||||
];
|
];
|
||||||
image = "nginx-container";
|
image = "nginx-container";
|
||||||
imageFile = pkgs.dockerTools.examples.nginx;
|
imageStream = pkgs.dockerTools.examples.nginxStream;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,6 +17,52 @@ let
|
|||||||
};
|
};
|
||||||
evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; };
|
evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; };
|
||||||
|
|
||||||
|
nginxArguments = let
|
||||||
|
nginxPort = "80";
|
||||||
|
nginxConf = pkgs.writeText "nginx.conf" ''
|
||||||
|
user nobody nobody;
|
||||||
|
daemon off;
|
||||||
|
error_log /dev/stdout info;
|
||||||
|
pid /dev/null;
|
||||||
|
events {}
|
||||||
|
http {
|
||||||
|
access_log /dev/stdout;
|
||||||
|
server {
|
||||||
|
listen ${nginxPort};
|
||||||
|
index index.html;
|
||||||
|
location / {
|
||||||
|
root ${nginxWebRoot};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
nginxWebRoot = pkgs.writeTextDir "index.html" ''
|
||||||
|
<html><body><h1>Hello from NGINX</h1></body></html>
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{ name = "nginx-container";
|
||||||
|
tag = "latest";
|
||||||
|
contents = [
|
||||||
|
fakeNss
|
||||||
|
pkgs.nginx
|
||||||
|
];
|
||||||
|
|
||||||
|
extraCommands = ''
|
||||||
|
mkdir -p tmp/nginx_client_body
|
||||||
|
|
||||||
|
# nginx still tries to read this directory even if error_log
|
||||||
|
# directive is specifying another file :/
|
||||||
|
mkdir -p var/log/nginx
|
||||||
|
'';
|
||||||
|
|
||||||
|
config = {
|
||||||
|
Cmd = [ "nginx" "-c" nginxConf ];
|
||||||
|
ExposedPorts = {
|
||||||
|
"${nginxPort}/tcp" = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
rec {
|
rec {
|
||||||
@ -60,52 +106,10 @@ rec {
|
|||||||
};
|
};
|
||||||
|
|
||||||
# 3. another service example
|
# 3. another service example
|
||||||
nginx = let
|
nginx = buildLayeredImage nginxArguments;
|
||||||
nginxPort = "80";
|
|
||||||
nginxConf = pkgs.writeText "nginx.conf" ''
|
|
||||||
user nobody nobody;
|
|
||||||
daemon off;
|
|
||||||
error_log /dev/stdout info;
|
|
||||||
pid /dev/null;
|
|
||||||
events {}
|
|
||||||
http {
|
|
||||||
access_log /dev/stdout;
|
|
||||||
server {
|
|
||||||
listen ${nginxPort};
|
|
||||||
index index.html;
|
|
||||||
location / {
|
|
||||||
root ${nginxWebRoot};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
nginxWebRoot = pkgs.writeTextDir "index.html" ''
|
|
||||||
<html><body><h1>Hello from NGINX</h1></body></html>
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
buildLayeredImage {
|
|
||||||
name = "nginx-container";
|
|
||||||
tag = "latest";
|
|
||||||
contents = [
|
|
||||||
fakeNss
|
|
||||||
pkgs.nginx
|
|
||||||
];
|
|
||||||
|
|
||||||
extraCommands = ''
|
# Used to demonstrate how virtualisation.oci-containers.imageStream works
|
||||||
mkdir -p tmp/nginx_client_body
|
nginxStream = pkgs.dockerTools.streamLayeredImage nginxArguments;
|
||||||
|
|
||||||
# nginx still tries to read this directory even if error_log
|
|
||||||
# directive is specifying another file :/
|
|
||||||
mkdir -p var/log/nginx
|
|
||||||
'';
|
|
||||||
|
|
||||||
config = {
|
|
||||||
Cmd = [ "nginx" "-c" nginxConf ];
|
|
||||||
ExposedPorts = {
|
|
||||||
"${nginxPort}/tcp" = {};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# 4. example of pulling an image. could be used as a base for other images
|
# 4. example of pulling an image. could be used as a base for other images
|
||||||
nixFromDockerHub = pullImage {
|
nixFromDockerHub = pullImage {
|
||||||
|
Loading…
Reference in New Issue
Block a user