linux-container.scm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2015 David Thompson <davet@gnu.org>
  3. ;;; Copyright © 2016-2017, 2019-2022 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. ;;; Copyright © 2022 Ricardo Wurmus <rekado@elephly.net>
  8. ;;;
  9. ;;; This file is part of GNU Guix.
  10. ;;;
  11. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  12. ;;; under the terms of the GNU General Public License as published by
  13. ;;; the Free Software Foundation; either version 3 of the License, or (at
  14. ;;; your option) any later version.
  15. ;;;
  16. ;;; GNU Guix is distributed in the hope that it will be useful, but
  17. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  18. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. ;;; GNU General Public License for more details.
  20. ;;;
  21. ;;; You should have received a copy of the GNU General Public License
  22. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  23. (define-module (gnu system linux-container)
  24. #:use-module (ice-9 match)
  25. #:use-module (srfi srfi-1)
  26. #:use-module (guix config)
  27. #:use-module (guix store)
  28. #:use-module (guix gexp)
  29. #:use-module (guix derivations)
  30. #:use-module (guix monads)
  31. #:use-module (guix modules)
  32. #:use-module (gnu build linux-container)
  33. #:use-module (gnu services)
  34. #:use-module (gnu services base)
  35. #:use-module (gnu services networking)
  36. #:use-module (gnu services shepherd)
  37. #:use-module (gnu system)
  38. #:use-module (gnu system file-systems)
  39. #:export (system-container
  40. containerized-operating-system
  41. container-script
  42. eval/container))
  43. (define* (container-essential-services os #:key shared-network?)
  44. "Return a list of essential services corresponding to OS, a
  45. non-containerized OS. This procedure essentially strips essential services
  46. from OS that are needed on the bare metal and not in a container."
  47. (define base
  48. (remove (lambda (service)
  49. (memq (service-kind service)
  50. (list (service-kind %linux-bare-metal-service)
  51. firmware-service-type
  52. system-service-type)))
  53. (operating-system-default-essential-services os)))
  54. (cons (service system-service-type
  55. `(("locale" ,(operating-system-locale-directory os))))
  56. ;; If network is to be shared with the host, remove network
  57. ;; configuration files from etc-service.
  58. (if shared-network?
  59. (modify-services base
  60. (etc-service-type
  61. files => (remove
  62. (match-lambda
  63. ((filename _)
  64. (member filename
  65. (map basename %network-configuration-files))))
  66. files)))
  67. base)))
  68. (define dummy-networking-service-type
  69. (shepherd-service-type
  70. 'dummy-networking
  71. (const (shepherd-service
  72. (documentation "Provide loopback and networking without actually
  73. doing anything.")
  74. (provision '(loopback networking))
  75. (start #~(const #t))))
  76. #f
  77. (description "Provide loopback and networking without actually doing
  78. anything. This service is used by guest systems running in containers, where
  79. networking support is provided by the host.")))
  80. (define %nscd-container-caches
  81. ;; Similar to %nscd-default-caches but with smaller cache sizes. This allows
  82. ;; many containers to coexist on the same machine without exhausting RAM.
  83. (map (lambda (cache)
  84. (nscd-cache
  85. (inherit cache)
  86. (max-database-size (expt 2 18)))) ;256KiB
  87. %nscd-default-caches))
  88. (define* (containerized-operating-system os mappings
  89. #:key
  90. shared-network?
  91. (extra-file-systems '()))
  92. "Return an operating system based on OS for use in a Linux container
  93. environment. MAPPINGS is a list of <file-system-mapping> to realize in the
  94. containerized OS. EXTRA-FILE-SYSTEMS is a list of file systems to add to OS."
  95. (define user-file-systems
  96. (remove (lambda (fs)
  97. (let ((target (file-system-mount-point fs))
  98. (source (file-system-device fs)))
  99. (or (string=? target (%store-prefix))
  100. (string=? target "/")
  101. (and (string? source)
  102. (string-prefix? "/dev/" source))
  103. (string-prefix? "/dev/" target)
  104. (string-prefix? "/sys/" target))))
  105. (operating-system-file-systems os)))
  106. (define (mapping->fs fs)
  107. (file-system (inherit (file-system-mapping->bind-mount fs))
  108. (needed-for-boot? #t)))
  109. (define services-to-drop
  110. ;; Service types to filter from the original operating-system. Some of
  111. ;; these make no sense in a container (e.g., those that access
  112. ;; /dev/tty[0-9]), while others just need to be reinstantiated with
  113. ;; different configs that are better suited to containers.
  114. (append (list console-font-service-type
  115. mingetty-service-type
  116. agetty-service-type
  117. ;; Reinstantiated below with smaller caches.
  118. nscd-service-type)
  119. (if shared-network?
  120. ;; Replace these with dummy-networking-service-type below.
  121. (list
  122. static-networking-service-type
  123. dhcp-client-service-type
  124. network-manager-service-type
  125. connman-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. (srfi srfi-37)
  193. (ice-9 match))
  194. (define (show-help)
  195. (display (G_ "Usage: run-container [OPTION ...]
  196. Run the container with the given options."))
  197. (newline)
  198. (display (G_ "
  199. --share=SPEC share host file system with read/write access
  200. according to SPEC"))
  201. (display (G_ "
  202. --expose=SPEC expose host file system directory as read-only
  203. according to SPEC"))
  204. (newline)
  205. (display (G_ "
  206. -h, --help display this help and exit"))
  207. (newline))
  208. (define %options
  209. ;; Specifications of the command-line options.
  210. (list (option '(#\h "help") #f #f
  211. (lambda args
  212. (show-help)
  213. (exit 0)))
  214. (option '("share") #t #f
  215. (lambda (opt name arg result)
  216. (alist-cons 'file-system-mapping
  217. (specification->file-system-mapping arg #t)
  218. result)))
  219. (option '("expose") #t #f
  220. (lambda (opt name arg result)
  221. (alist-cons 'file-system-mapping
  222. (specification->file-system-mapping arg #f)
  223. result)))))
  224. (define (parse-options args options)
  225. (args-fold args options
  226. (lambda (opt name arg . rest)
  227. (report-error (G_ "~A: unrecognized option~%") name)
  228. (exit 1))
  229. (lambda (op res) (cons op res))
  230. '()))
  231. (define (explain pid)
  232. ;; XXX: We can't quite call 'bindtextdomain' so there's actually
  233. ;; no i18n.
  234. ;; XXX: Should we really give both options? 'guix container exec'
  235. ;; is a more verbose command. Hard to fail to enter the container
  236. ;; when we list two options.
  237. (info (G_ "system container is running as PID ~a~%") pid)
  238. (info (G_ "Run 'sudo guix container exec ~a /run/current-system/profile/bin/bash --login'\n")
  239. pid)
  240. (info (G_ "or run 'sudo nsenter -a -t ~a' to get a shell into it.~%") pid)
  241. (newline (guix-warning-port)))
  242. (let* ((opts (parse-options (cdr (command-line)) %options))
  243. (mappings (filter-map (match-lambda
  244. (('file-system-mapping . mapping) mapping)
  245. (_ #f))
  246. opts))
  247. (file-systems
  248. (filter-map (lambda (fs)
  249. (let ((flags (file-system-flags fs)))
  250. (and (or (not (memq 'bind-mount flags))
  251. (file-exists? (file-system-device fs)))
  252. fs)))
  253. (append (map file-system-mapping->bind-mount mappings)
  254. (map spec->file-system '#$specs)))))
  255. (call-with-container file-systems
  256. (lambda ()
  257. (setenv "HOME" "/root")
  258. (setenv "TMPDIR" "/tmp")
  259. (setenv "GUIX_NEW_SYSTEM" #$os)
  260. (for-each mkdir-p '("/run" "/bin" "/etc" "/home" "/var"))
  261. (primitive-load (string-append #$os "/boot")))
  262. ;; A range of 65536 uid/gids is used to cover 16 bits worth of
  263. ;; users and groups, which is sufficient for most cases.
  264. ;;
  265. ;; See: http://www.freedesktop.org/software/systemd/man/systemd-nspawn.html#--private-users=
  266. #:host-uids 65536
  267. #:namespaces (if #$shared-network?
  268. (delq 'net %namespaces)
  269. %namespaces)
  270. #:process-spawned-hook explain)))))
  271. (gexp->script "run-container" script)))
  272. (define* (eval/container exp
  273. #:key
  274. (mappings '())
  275. (namespaces %namespaces)
  276. (guest-uid 0) (guest-gid 0))
  277. "Evaluate EXP, a gexp, in a new process executing in separate namespaces as
  278. listed in NAMESPACES. Add MAPPINGS, a list of <file-system-mapping>, to the
  279. set of directories visible in the process's mount namespace. Inside the
  280. namespaces, run code as GUEST-UID and GUEST-GID. Return the process' exit
  281. status as a monadic value.
  282. This is useful to implement processes that, unlike derivations, are not
  283. entirely pure and need to access the outside world or to perform side
  284. effects."
  285. (mlet %store-monad ((lowered (lower-gexp exp)))
  286. (define inputs
  287. (cons (lowered-gexp-guile lowered)
  288. (lowered-gexp-inputs lowered)))
  289. (define items
  290. (append (append-map derivation-input-output-paths inputs)
  291. (lowered-gexp-sources lowered)))
  292. (mbegin %store-monad
  293. (built-derivations inputs)
  294. (mlet %store-monad ((closure ((store-lift requisites) items)))
  295. (return (call-with-container (map file-system-mapping->bind-mount
  296. (append (map (lambda (item)
  297. (file-system-mapping
  298. (source item)
  299. (target source)))
  300. closure)
  301. mappings))
  302. (lambda ()
  303. (apply execl
  304. (string-append (derivation-input-output-path
  305. (lowered-gexp-guile lowered))
  306. "/bin/guile")
  307. "guile"
  308. (append (append-map (lambda (directory)
  309. `("-L" ,directory))
  310. (lowered-gexp-load-path lowered))
  311. (append-map (lambda (directory)
  312. `("-C" ,directory))
  313. (lowered-gexp-load-compiled-path
  314. lowered))
  315. (list "-c"
  316. (object->string
  317. (lowered-gexp-sexp lowered))))))
  318. #:namespaces namespaces
  319. #:guest-uid guest-uid
  320. #:guest-gid guest-gid))))))