runc.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. /*
  2. Copyright The containerd Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package runc
  14. import (
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "io"
  20. "io/ioutil"
  21. "os"
  22. "os/exec"
  23. "path/filepath"
  24. "strconv"
  25. "strings"
  26. "syscall"
  27. "time"
  28. specs "github.com/opencontainers/runtime-spec/specs-go"
  29. )
  30. // Format is the type of log formatting options avaliable
  31. type Format string
  32. // TopBody represents the structured data of the full ps output
  33. type TopResults struct {
  34. // Processes running in the container, where each is process is an array of values corresponding to the headers
  35. Processes [][]string `json:"Processes"`
  36. // Headers are the names of the columns
  37. Headers []string `json:"Headers"`
  38. }
  39. const (
  40. none Format = ""
  41. JSON Format = "json"
  42. Text Format = "text"
  43. // DefaultCommand is the default command for Runc
  44. DefaultCommand = "runc"
  45. )
  46. // Runc is the client to the runc cli
  47. type Runc struct {
  48. //If command is empty, DefaultCommand is used
  49. Command string
  50. Root string
  51. Debug bool
  52. Log string
  53. LogFormat Format
  54. PdeathSignal syscall.Signal
  55. Setpgid bool
  56. Criu string
  57. SystemdCgroup bool
  58. Rootless *bool // nil stands for "auto"
  59. }
  60. // List returns all containers created inside the provided runc root directory
  61. func (r *Runc) List(context context.Context) ([]*Container, error) {
  62. data, err := cmdOutput(r.command(context, "list", "--format=json"), false)
  63. if err != nil {
  64. return nil, err
  65. }
  66. var out []*Container
  67. if err := json.Unmarshal(data, &out); err != nil {
  68. return nil, err
  69. }
  70. return out, nil
  71. }
  72. // State returns the state for the container provided by id
  73. func (r *Runc) State(context context.Context, id string) (*Container, error) {
  74. data, err := cmdOutput(r.command(context, "state", id), true)
  75. if err != nil {
  76. return nil, fmt.Errorf("%s: %s", err, data)
  77. }
  78. var c Container
  79. if err := json.Unmarshal(data, &c); err != nil {
  80. return nil, err
  81. }
  82. return &c, nil
  83. }
  84. type ConsoleSocket interface {
  85. Path() string
  86. }
  87. type CreateOpts struct {
  88. IO
  89. // PidFile is a path to where a pid file should be created
  90. PidFile string
  91. ConsoleSocket ConsoleSocket
  92. Detach bool
  93. NoPivot bool
  94. NoNewKeyring bool
  95. ExtraFiles []*os.File
  96. }
  97. func (o *CreateOpts) args() (out []string, err error) {
  98. if o.PidFile != "" {
  99. abs, err := filepath.Abs(o.PidFile)
  100. if err != nil {
  101. return nil, err
  102. }
  103. out = append(out, "--pid-file", abs)
  104. }
  105. if o.ConsoleSocket != nil {
  106. out = append(out, "--console-socket", o.ConsoleSocket.Path())
  107. }
  108. if o.NoPivot {
  109. out = append(out, "--no-pivot")
  110. }
  111. if o.NoNewKeyring {
  112. out = append(out, "--no-new-keyring")
  113. }
  114. if o.Detach {
  115. out = append(out, "--detach")
  116. }
  117. if o.ExtraFiles != nil {
  118. out = append(out, "--preserve-fds", strconv.Itoa(len(o.ExtraFiles)))
  119. }
  120. return out, nil
  121. }
  122. // Create creates a new container and returns its pid if it was created successfully
  123. func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOpts) error {
  124. args := []string{"create", "--bundle", bundle}
  125. if opts != nil {
  126. oargs, err := opts.args()
  127. if err != nil {
  128. return err
  129. }
  130. args = append(args, oargs...)
  131. }
  132. cmd := r.command(context, append(args, id)...)
  133. if opts != nil && opts.IO != nil {
  134. opts.Set(cmd)
  135. }
  136. cmd.ExtraFiles = opts.ExtraFiles
  137. if cmd.Stdout == nil && cmd.Stderr == nil {
  138. data, err := cmdOutput(cmd, true)
  139. if err != nil {
  140. return fmt.Errorf("%s: %s", err, data)
  141. }
  142. return nil
  143. }
  144. ec, err := Monitor.Start(cmd)
  145. if err != nil {
  146. return err
  147. }
  148. if opts != nil && opts.IO != nil {
  149. if c, ok := opts.IO.(StartCloser); ok {
  150. if err := c.CloseAfterStart(); err != nil {
  151. return err
  152. }
  153. }
  154. }
  155. status, err := Monitor.Wait(cmd, ec)
  156. if err == nil && status != 0 {
  157. err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
  158. }
  159. return err
  160. }
  161. // Start will start an already created container
  162. func (r *Runc) Start(context context.Context, id string) error {
  163. return r.runOrError(r.command(context, "start", id))
  164. }
  165. type ExecOpts struct {
  166. IO
  167. PidFile string
  168. ConsoleSocket ConsoleSocket
  169. Detach bool
  170. }
  171. func (o *ExecOpts) args() (out []string, err error) {
  172. if o.ConsoleSocket != nil {
  173. out = append(out, "--console-socket", o.ConsoleSocket.Path())
  174. }
  175. if o.Detach {
  176. out = append(out, "--detach")
  177. }
  178. if o.PidFile != "" {
  179. abs, err := filepath.Abs(o.PidFile)
  180. if err != nil {
  181. return nil, err
  182. }
  183. out = append(out, "--pid-file", abs)
  184. }
  185. return out, nil
  186. }
  187. // Exec executres and additional process inside the container based on a full
  188. // OCI Process specification
  189. func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
  190. f, err := ioutil.TempFile(os.Getenv("XDG_RUNTIME_DIR"), "runc-process")
  191. if err != nil {
  192. return err
  193. }
  194. defer os.Remove(f.Name())
  195. err = json.NewEncoder(f).Encode(spec)
  196. f.Close()
  197. if err != nil {
  198. return err
  199. }
  200. args := []string{"exec", "--process", f.Name()}
  201. if opts != nil {
  202. oargs, err := opts.args()
  203. if err != nil {
  204. return err
  205. }
  206. args = append(args, oargs...)
  207. }
  208. cmd := r.command(context, append(args, id)...)
  209. if opts != nil && opts.IO != nil {
  210. opts.Set(cmd)
  211. }
  212. if cmd.Stdout == nil && cmd.Stderr == nil {
  213. data, err := cmdOutput(cmd, true)
  214. if err != nil {
  215. return fmt.Errorf("%s: %s", err, data)
  216. }
  217. return nil
  218. }
  219. ec, err := Monitor.Start(cmd)
  220. if err != nil {
  221. return err
  222. }
  223. if opts != nil && opts.IO != nil {
  224. if c, ok := opts.IO.(StartCloser); ok {
  225. if err := c.CloseAfterStart(); err != nil {
  226. return err
  227. }
  228. }
  229. }
  230. status, err := Monitor.Wait(cmd, ec)
  231. if err == nil && status != 0 {
  232. err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
  233. }
  234. return err
  235. }
  236. // Run runs the create, start, delete lifecycle of the container
  237. // and returns its exit status after it has exited
  238. func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
  239. args := []string{"run", "--bundle", bundle}
  240. if opts != nil {
  241. oargs, err := opts.args()
  242. if err != nil {
  243. return -1, err
  244. }
  245. args = append(args, oargs...)
  246. }
  247. cmd := r.command(context, append(args, id)...)
  248. if opts != nil && opts.IO != nil {
  249. opts.Set(cmd)
  250. }
  251. ec, err := Monitor.Start(cmd)
  252. if err != nil {
  253. return -1, err
  254. }
  255. status, err := Monitor.Wait(cmd, ec)
  256. if err == nil && status != 0 {
  257. err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
  258. }
  259. return status, err
  260. }
  261. type DeleteOpts struct {
  262. Force bool
  263. }
  264. func (o *DeleteOpts) args() (out []string) {
  265. if o.Force {
  266. out = append(out, "--force")
  267. }
  268. return out
  269. }
  270. // Delete deletes the container
  271. func (r *Runc) Delete(context context.Context, id string, opts *DeleteOpts) error {
  272. args := []string{"delete"}
  273. if opts != nil {
  274. args = append(args, opts.args()...)
  275. }
  276. return r.runOrError(r.command(context, append(args, id)...))
  277. }
  278. // KillOpts specifies options for killing a container and its processes
  279. type KillOpts struct {
  280. All bool
  281. }
  282. func (o *KillOpts) args() (out []string) {
  283. if o.All {
  284. out = append(out, "--all")
  285. }
  286. return out
  287. }
  288. // Kill sends the specified signal to the container
  289. func (r *Runc) Kill(context context.Context, id string, sig int, opts *KillOpts) error {
  290. args := []string{
  291. "kill",
  292. }
  293. if opts != nil {
  294. args = append(args, opts.args()...)
  295. }
  296. return r.runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...))
  297. }
  298. // Stats return the stats for a container like cpu, memory, and io
  299. func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
  300. cmd := r.command(context, "events", "--stats", id)
  301. rd, err := cmd.StdoutPipe()
  302. if err != nil {
  303. return nil, err
  304. }
  305. ec, err := Monitor.Start(cmd)
  306. if err != nil {
  307. return nil, err
  308. }
  309. defer func() {
  310. rd.Close()
  311. Monitor.Wait(cmd, ec)
  312. }()
  313. var e Event
  314. if err := json.NewDecoder(rd).Decode(&e); err != nil {
  315. return nil, err
  316. }
  317. return e.Stats, nil
  318. }
  319. // Events returns an event stream from runc for a container with stats and OOM notifications
  320. func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
  321. cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
  322. rd, err := cmd.StdoutPipe()
  323. if err != nil {
  324. return nil, err
  325. }
  326. ec, err := Monitor.Start(cmd)
  327. if err != nil {
  328. rd.Close()
  329. return nil, err
  330. }
  331. var (
  332. dec = json.NewDecoder(rd)
  333. c = make(chan *Event, 128)
  334. )
  335. go func() {
  336. defer func() {
  337. close(c)
  338. rd.Close()
  339. Monitor.Wait(cmd, ec)
  340. }()
  341. for {
  342. var e Event
  343. if err := dec.Decode(&e); err != nil {
  344. if err == io.EOF {
  345. return
  346. }
  347. e = Event{
  348. Type: "error",
  349. Err: err,
  350. }
  351. }
  352. c <- &e
  353. }
  354. }()
  355. return c, nil
  356. }
  357. // Pause the container with the provided id
  358. func (r *Runc) Pause(context context.Context, id string) error {
  359. return r.runOrError(r.command(context, "pause", id))
  360. }
  361. // Resume the container with the provided id
  362. func (r *Runc) Resume(context context.Context, id string) error {
  363. return r.runOrError(r.command(context, "resume", id))
  364. }
  365. // Ps lists all the processes inside the container returning their pids
  366. func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
  367. data, err := cmdOutput(r.command(context, "ps", "--format", "json", id), true)
  368. if err != nil {
  369. return nil, fmt.Errorf("%s: %s", err, data)
  370. }
  371. var pids []int
  372. if err := json.Unmarshal(data, &pids); err != nil {
  373. return nil, err
  374. }
  375. return pids, nil
  376. }
  377. // Top lists all the processes inside the container returning the full ps data
  378. func (r *Runc) Top(context context.Context, id string, psOptions string) (*TopResults, error) {
  379. data, err := cmdOutput(r.command(context, "ps", "--format", "table", id, psOptions), true)
  380. if err != nil {
  381. return nil, fmt.Errorf("%s: %s", err, data)
  382. }
  383. topResults, err := ParsePSOutput(data)
  384. if err != nil {
  385. return nil, fmt.Errorf("%s: ", err)
  386. }
  387. return topResults, nil
  388. }
  389. type CheckpointOpts struct {
  390. // ImagePath is the path for saving the criu image file
  391. ImagePath string
  392. // WorkDir is the working directory for criu
  393. WorkDir string
  394. // ParentPath is the path for previous image files from a pre-dump
  395. ParentPath string
  396. // AllowOpenTCP allows open tcp connections to be checkpointed
  397. AllowOpenTCP bool
  398. // AllowExternalUnixSockets allows external unix sockets to be checkpointed
  399. AllowExternalUnixSockets bool
  400. // AllowTerminal allows the terminal(pty) to be checkpointed with a container
  401. AllowTerminal bool
  402. // CriuPageServer is the address:port for the criu page server
  403. CriuPageServer string
  404. // FileLocks handle file locks held by the container
  405. FileLocks bool
  406. // Cgroups is the cgroup mode for how to handle the checkpoint of a container's cgroups
  407. Cgroups CgroupMode
  408. // EmptyNamespaces creates a namespace for the container but does not save its properties
  409. // Provide the namespaces you wish to be checkpointed without their settings on restore
  410. EmptyNamespaces []string
  411. }
  412. type CgroupMode string
  413. const (
  414. Soft CgroupMode = "soft"
  415. Full CgroupMode = "full"
  416. Strict CgroupMode = "strict"
  417. )
  418. func (o *CheckpointOpts) args() (out []string) {
  419. if o.ImagePath != "" {
  420. out = append(out, "--image-path", o.ImagePath)
  421. }
  422. if o.WorkDir != "" {
  423. out = append(out, "--work-path", o.WorkDir)
  424. }
  425. if o.ParentPath != "" {
  426. out = append(out, "--parent-path", o.ParentPath)
  427. }
  428. if o.AllowOpenTCP {
  429. out = append(out, "--tcp-established")
  430. }
  431. if o.AllowExternalUnixSockets {
  432. out = append(out, "--ext-unix-sk")
  433. }
  434. if o.AllowTerminal {
  435. out = append(out, "--shell-job")
  436. }
  437. if o.CriuPageServer != "" {
  438. out = append(out, "--page-server", o.CriuPageServer)
  439. }
  440. if o.FileLocks {
  441. out = append(out, "--file-locks")
  442. }
  443. if string(o.Cgroups) != "" {
  444. out = append(out, "--manage-cgroups-mode", string(o.Cgroups))
  445. }
  446. for _, ns := range o.EmptyNamespaces {
  447. out = append(out, "--empty-ns", ns)
  448. }
  449. return out
  450. }
  451. type CheckpointAction func([]string) []string
  452. // LeaveRunning keeps the container running after the checkpoint has been completed
  453. func LeaveRunning(args []string) []string {
  454. return append(args, "--leave-running")
  455. }
  456. // PreDump allows a pre-dump of the checkpoint to be made and completed later
  457. func PreDump(args []string) []string {
  458. return append(args, "--pre-dump")
  459. }
  460. // Checkpoint allows you to checkpoint a container using criu
  461. func (r *Runc) Checkpoint(context context.Context, id string, opts *CheckpointOpts, actions ...CheckpointAction) error {
  462. args := []string{"checkpoint"}
  463. if opts != nil {
  464. args = append(args, opts.args()...)
  465. }
  466. for _, a := range actions {
  467. args = a(args)
  468. }
  469. return r.runOrError(r.command(context, append(args, id)...))
  470. }
  471. type RestoreOpts struct {
  472. CheckpointOpts
  473. IO
  474. Detach bool
  475. PidFile string
  476. NoSubreaper bool
  477. NoPivot bool
  478. ConsoleSocket ConsoleSocket
  479. }
  480. func (o *RestoreOpts) args() ([]string, error) {
  481. out := o.CheckpointOpts.args()
  482. if o.Detach {
  483. out = append(out, "--detach")
  484. }
  485. if o.PidFile != "" {
  486. abs, err := filepath.Abs(o.PidFile)
  487. if err != nil {
  488. return nil, err
  489. }
  490. out = append(out, "--pid-file", abs)
  491. }
  492. if o.ConsoleSocket != nil {
  493. out = append(out, "--console-socket", o.ConsoleSocket.Path())
  494. }
  495. if o.NoPivot {
  496. out = append(out, "--no-pivot")
  497. }
  498. if o.NoSubreaper {
  499. out = append(out, "-no-subreaper")
  500. }
  501. return out, nil
  502. }
  503. // Restore restores a container with the provide id from an existing checkpoint
  504. func (r *Runc) Restore(context context.Context, id, bundle string, opts *RestoreOpts) (int, error) {
  505. args := []string{"restore"}
  506. if opts != nil {
  507. oargs, err := opts.args()
  508. if err != nil {
  509. return -1, err
  510. }
  511. args = append(args, oargs...)
  512. }
  513. args = append(args, "--bundle", bundle)
  514. cmd := r.command(context, append(args, id)...)
  515. if opts != nil && opts.IO != nil {
  516. opts.Set(cmd)
  517. }
  518. ec, err := Monitor.Start(cmd)
  519. if err != nil {
  520. return -1, err
  521. }
  522. if opts != nil && opts.IO != nil {
  523. if c, ok := opts.IO.(StartCloser); ok {
  524. if err := c.CloseAfterStart(); err != nil {
  525. return -1, err
  526. }
  527. }
  528. }
  529. status, err := Monitor.Wait(cmd, ec)
  530. if err == nil && status != 0 {
  531. err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
  532. }
  533. return status, err
  534. }
  535. // Update updates the current container with the provided resource spec
  536. func (r *Runc) Update(context context.Context, id string, resources *specs.LinuxResources) error {
  537. buf := getBuf()
  538. defer putBuf(buf)
  539. if err := json.NewEncoder(buf).Encode(resources); err != nil {
  540. return err
  541. }
  542. args := []string{"update", "--resources", "-", id}
  543. cmd := r.command(context, args...)
  544. cmd.Stdin = buf
  545. return r.runOrError(cmd)
  546. }
  547. var ErrParseRuncVersion = errors.New("unable to parse runc version")
  548. type Version struct {
  549. Runc string
  550. Commit string
  551. Spec string
  552. }
  553. // Version returns the runc and runtime-spec versions
  554. func (r *Runc) Version(context context.Context) (Version, error) {
  555. data, err := cmdOutput(r.command(context, "--version"), false)
  556. if err != nil {
  557. return Version{}, err
  558. }
  559. return parseVersion(data)
  560. }
  561. func parseVersion(data []byte) (Version, error) {
  562. var v Version
  563. parts := strings.Split(strings.TrimSpace(string(data)), "\n")
  564. if len(parts) != 3 {
  565. return v, nil
  566. }
  567. for i, p := range []struct {
  568. dest *string
  569. split string
  570. }{
  571. {
  572. dest: &v.Runc,
  573. split: "version ",
  574. },
  575. {
  576. dest: &v.Commit,
  577. split: ": ",
  578. },
  579. {
  580. dest: &v.Spec,
  581. split: ": ",
  582. },
  583. } {
  584. p2 := strings.Split(parts[i], p.split)
  585. if len(p2) != 2 {
  586. return v, fmt.Errorf("unable to parse version line %q", parts[i])
  587. }
  588. *p.dest = p2[1]
  589. }
  590. return v, nil
  591. }
  592. func (r *Runc) args() (out []string) {
  593. if r.Root != "" {
  594. out = append(out, "--root", r.Root)
  595. }
  596. if r.Debug {
  597. out = append(out, "--debug")
  598. }
  599. if r.Log != "" {
  600. out = append(out, "--log", r.Log)
  601. }
  602. if r.LogFormat != none {
  603. out = append(out, "--log-format", string(r.LogFormat))
  604. }
  605. if r.Criu != "" {
  606. out = append(out, "--criu", r.Criu)
  607. }
  608. if r.SystemdCgroup {
  609. out = append(out, "--systemd-cgroup")
  610. }
  611. if r.Rootless != nil {
  612. // nil stands for "auto" (differs from explicit "false")
  613. out = append(out, "--rootless="+strconv.FormatBool(*r.Rootless))
  614. }
  615. return out
  616. }
  617. // runOrError will run the provided command. If an error is
  618. // encountered and neither Stdout or Stderr was set the error and the
  619. // stderr of the command will be returned in the format of <error>:
  620. // <stderr>
  621. func (r *Runc) runOrError(cmd *exec.Cmd) error {
  622. if cmd.Stdout != nil || cmd.Stderr != nil {
  623. ec, err := Monitor.Start(cmd)
  624. if err != nil {
  625. return err
  626. }
  627. status, err := Monitor.Wait(cmd, ec)
  628. if err == nil && status != 0 {
  629. err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
  630. }
  631. return err
  632. }
  633. data, err := cmdOutput(cmd, true)
  634. if err != nil {
  635. return fmt.Errorf("%s: %s", err, data)
  636. }
  637. return nil
  638. }
  639. func cmdOutput(cmd *exec.Cmd, combined bool) ([]byte, error) {
  640. b := getBuf()
  641. defer putBuf(b)
  642. cmd.Stdout = b
  643. if combined {
  644. cmd.Stderr = b
  645. }
  646. ec, err := Monitor.Start(cmd)
  647. if err != nil {
  648. return nil, err
  649. }
  650. status, err := Monitor.Wait(cmd, ec)
  651. if err == nil && status != 0 {
  652. err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
  653. }
  654. return b.Bytes(), err
  655. }