linux-container.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2015 David Thompson <davet@gnu.org>
  3. ;;; Copyright © 2016, 2017, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
  4. ;;; Copyright © 2019 Arun Isaac <arunisaac@systemreboot.net>
  5. ;;; Copyright © 2020 Efraim Flashner <efraim@flashner.co.il>
  6. ;;; Copyright © 2020 Google LLC
  7. ;;;
  8. ;;; This file is part of GNU Guix.
  9. ;;;
  10. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  11. ;;; under the terms of the GNU General Public License as published by
  12. ;;; the Free Software Foundation; either version 3 of the License, or (at
  13. ;;; your option) any later version.
  14. ;;;
  15. ;;; GNU Guix is distributed in the hope that it will be useful, but
  16. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;;; GNU General Public License for more details.
  19. ;;;
  20. ;;; You should have received a copy of the GNU General Public License
  21. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  22. (define-module (gnu system linux-container)
  23. #:use-module (ice-9 match)
  24. #:use-module (srfi srfi-1)
  25. #:use-module (guix config)
  26. #:use-module (guix store)
  27. #:use-module (guix gexp)
  28. #:use-module (guix derivations)
  29. #:use-module (guix monads)
  30. #:use-module (guix modules)
  31. #:use-module (gnu build linux-container)
  32. #:use-module (gnu services)
  33. #:use-module (gnu services base)
  34. #:use-module (gnu services networking)
  35. #:use-module (gnu services shepherd)
  36. #:use-module (gnu system)
  37. #:use-module (gnu system file-systems)
  38. #:export (system-container
  39. containerized-operating-system
  40. container-script
  41. eval/container))
  42. (define* (container-essential-services os #:key shared-network?)
  43. "Return a list of essential services corresponding to OS, a
  44. non-containerized OS. This procedure essentially strips essential services
  45. from OS that are needed on the bare metal and not in a container."
  46. (define base
  47. (remove (lambda (service)
  48. (memq (service-kind service)
  49. (list (service-kind %linux-bare-metal-service)
  50. firmware-service-type
  51. system-service-type)))
  52. (operating-system-default-essential-services os)))
  53. (cons (service system-service-type
  54. `(("locale" ,(operating-system-locale-directory os))))
  55. ;; If network is to be shared with the host, remove network
  56. ;; configuration files from etc-service.
  57. (if shared-network?
  58. (modify-services base
  59. (etc-service-type
  60. files => (remove
  61. (match-lambda
  62. ((filename _)
  63. (member filename
  64. (map basename %network-configuration-files))))
  65. files)))
  66. base)))
  67. (define dummy-networking-service-type
  68. (shepherd-service-type
  69. 'dummy-networking
  70. (const (shepherd-service
  71. (documentation "Provide loopback and networking without actually
  72. doing anything.")
  73. (provision '(loopback networking))
  74. (start #~(const #t))))
  75. #f
  76. (description "Provide loopback and networking without actually doing
  77. anything. This service is used by guest systems running in containers, where
  78. networking support is provided by the host.")))
  79. (define %nscd-container-caches
  80. ;; Similar to %nscd-default-caches but with smaller cache sizes. This allows
  81. ;; many containers to coexist on the same machine without exhausting RAM.
  82. (map (lambda (cache)
  83. (nscd-cache
  84. (inherit cache)
  85. (max-database-size (expt 2 18)))) ;256KiB
  86. %nscd-default-caches))
  87. (define* (containerized-operating-system os mappings
  88. #:key
  89. shared-network?
  90. (extra-file-systems '()))
  91. "Return an operating system based on OS for use in a Linux container
  92. environment. MAPPINGS is a list of <file-system-mapping> to realize in the
  93. containerized OS. EXTRA-FILE-SYSTEMS is a list of file systems to add to OS."
  94. (define user-file-systems
  95. (remove (lambda (fs)
  96. (let ((target (file-system-mount-point fs))
  97. (source (file-system-device fs)))
  98. (or (string=? target (%store-prefix))
  99. (string=? target "/")
  100. (and (string? source)
  101. (string-prefix? "/dev/" source))
  102. (string-prefix? "/dev/" target)
  103. (string-prefix? "/sys/" target))))
  104. (operating-system-file-systems os)))
  105. (define (mapping->fs fs)
  106. (file-system (inherit (file-system-mapping->bind-mount fs))
  107. (needed-for-boot? #t)))
  108. (define services-to-drop
  109. ;; Service types to filter from the original operating-system. Some of
  110. ;; these make no sense in a container (e.g., those that access
  111. ;; /dev/tty[0-9]), while others just need to be reinstantiated with
  112. ;; different configs that are better suited to containers.
  113. (append (list console-font-service-type
  114. mingetty-service-type
  115. agetty-service-type
  116. ;; Reinstantiated below with smaller caches.
  117. nscd-service-type)
  118. (if shared-network?
  119. ;; Replace these with dummy-networking-service-type below.
  120. (list
  121. static-networking-service-type
  122. dhcp-client-service-type
  123. network-manager-service-type
  124. connman-service-type
  125. wicd-service-type)
  126. (list))))
  127. (define services-to-add
  128. (append
  129. ;; Many Guix services depend on a 'networking' shepherd
  130. ;; service, so make sure to provide a dummy 'networking'
  131. ;; service when we are sure that networking is already set up
  132. ;; in the host and can be used. That prevents double setup.
  133. (if shared-network?
  134. (list (service dummy-networking-service-type))
  135. '())
  136. (list
  137. (nscd-service (nscd-configuration
  138. (caches %nscd-container-caches))))))
  139. (operating-system
  140. (inherit os)
  141. (swap-devices '()) ; disable swap
  142. (essential-services (container-essential-services
  143. this-operating-system
  144. #:shared-network? shared-network?))
  145. (services (append (remove (lambda (service)
  146. (memq (service-kind service)
  147. services-to-drop))
  148. (operating-system-user-services os))
  149. services-to-add))
  150. (file-systems (append (map mapping->fs
  151. (if shared-network?
  152. (append %network-file-mappings mappings)
  153. mappings))
  154. extra-file-systems
  155. user-file-systems
  156. ;; Provide a dummy root file system so we can create
  157. ;; a 'boot-parameters' file.
  158. (list (file-system
  159. (mount-point "/")
  160. (device "nothing")
  161. (type "dummy")))))))
  162. (define* (container-script os #:key (mappings '()) shared-network?)
  163. "Return a derivation of a script that runs OS as a Linux container.
  164. MAPPINGS is a list of <file-system> objects that specify the files/directories
  165. that will be shared with the host system."
  166. (define (mountable-file-system? file-system)
  167. ;; Return #t if FILE-SYSTEM should be mounted in the container.
  168. (and (not (string=? "/" (file-system-mount-point file-system)))
  169. (file-system-needed-for-boot? file-system)))
  170. (define (os-file-system-specs os)
  171. (map file-system->spec
  172. (filter mountable-file-system?
  173. (operating-system-file-systems os))))
  174. (let* ((os (containerized-operating-system
  175. os (cons %store-mapping mappings)
  176. #:shared-network? shared-network?
  177. #:extra-file-systems %container-file-systems))
  178. (specs (os-file-system-specs os)))
  179. (define script
  180. (with-imported-modules (source-module-closure
  181. '((guix build utils)
  182. (gnu build linux-container)
  183. (guix i18n)
  184. (guix diagnostics)))
  185. #~(begin
  186. (use-modules (gnu build linux-container)
  187. (gnu system file-systems) ;spec->file-system
  188. (guix build utils)
  189. (guix i18n)
  190. (guix diagnostics)
  191. (srfi srfi-1))
  192. (define file-systems
  193. (filter-map (lambda (spec)
  194. (let* ((fs (spec->file-system spec))
  195. (flags (file-system-flags fs)))
  196. (and (or (not (memq 'bind-mount flags))
  197. (file-exists? (file-system-device fs)))
  198. fs)))
  199. '#$specs))
  200. (define (explain pid)
  201. ;; XXX: We can't quite call 'bindtextdomain' so there's actually
  202. ;; no i18n.
  203. ;; XXX: Should we really give both options? 'guix container exec'
  204. ;; is a more verbose command. Hard to fail to enter the container
  205. ;; when we list two options.
  206. (info (G_ "system container is running as PID ~a~%") pid)
  207. (info (G_ "Run 'sudo guix container exec ~a /run/current-system/profile/bin/bash --login'\n")
  208. pid)
  209. (info (G_ "or run 'sudo nsenter -a -t ~a' to get a shell into it.~%") pid)
  210. (newline (guix-warning-port)))
  211. (call-with-container file-systems
  212. (lambda ()
  213. (setenv "HOME" "/root")
  214. (setenv "TMPDIR" "/tmp")
  215. (setenv "GUIX_NEW_SYSTEM" #$os)
  216. (for-each mkdir-p '("/run" "/bin" "/etc" "/home" "/var"))
  217. (primitive-load (string-append #$os "/boot")))
  218. ;; A range of 65536 uid/gids is used to cover 16 bits worth of
  219. ;; users and groups, which is sufficient for most cases.
  220. ;;
  221. ;; See: http://www.freedesktop.org/software/systemd/man/systemd-nspawn.html#--private-users=
  222. #:host-uids 65536
  223. #:namespaces (if #$shared-network?
  224. (delq 'net %namespaces)
  225. %namespaces)
  226. #:process-spawned-hook explain))))
  227. (gexp->script "run-container" script)))
  228. (define* (eval/container exp
  229. #:key
  230. (mappings '())
  231. (namespaces %namespaces))
  232. "Evaluate EXP, a gexp, in a new process executing in separate namespaces as
  233. listed in NAMESPACES. Add MAPPINGS, a list of <file-system-mapping>, to the
  234. set of directories visible in the process's mount namespace. Return the
  235. process' exit status as a monadic value.
  236. This is useful to implement processes that, unlike derivations, are not
  237. entirely pure and need to access the outside world or to perform side
  238. effects."
  239. (mlet %store-monad ((lowered (lower-gexp exp)))
  240. (define inputs
  241. (cons (lowered-gexp-guile lowered)
  242. (lowered-gexp-inputs lowered)))
  243. (define items
  244. (append (append-map derivation-input-output-paths inputs)
  245. (lowered-gexp-sources lowered)))
  246. (mbegin %store-monad
  247. (built-derivations inputs)
  248. (mlet %store-monad ((closure ((store-lift requisites) items)))
  249. (return (call-with-container (map file-system-mapping->bind-mount
  250. (append (map (lambda (item)
  251. (file-system-mapping
  252. (source item)
  253. (target source)))
  254. closure)
  255. mappings))
  256. (lambda ()
  257. (apply execl
  258. (string-append (derivation-input-output-path
  259. (lowered-gexp-guile lowered))
  260. "/bin/guile")
  261. "guile"
  262. (append (append-map (lambda (directory)
  263. `("-L" ,directory))
  264. (lowered-gexp-load-path lowered))
  265. (append-map (lambda (directory)
  266. `("-C" ,directory))
  267. (lowered-gexp-load-compiled-path
  268. lowered))
  269. (list "-c"
  270. (object->string
  271. (lowered-gexp-sexp lowered))))))))))))