runc.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  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 sucessfully", 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 sucessfully", 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. return Monitor.Wait(cmd, ec)
  256. }
  257. type DeleteOpts struct {
  258. Force bool
  259. }
  260. func (o *DeleteOpts) args() (out []string) {
  261. if o.Force {
  262. out = append(out, "--force")
  263. }
  264. return out
  265. }
  266. // Delete deletes the container
  267. func (r *Runc) Delete(context context.Context, id string, opts *DeleteOpts) error {
  268. args := []string{"delete"}
  269. if opts != nil {
  270. args = append(args, opts.args()...)
  271. }
  272. return r.runOrError(r.command(context, append(args, id)...))
  273. }
  274. // KillOpts specifies options for killing a container and its processes
  275. type KillOpts struct {
  276. All bool
  277. }
  278. func (o *KillOpts) args() (out []string) {
  279. if o.All {
  280. out = append(out, "--all")
  281. }
  282. return out
  283. }
  284. // Kill sends the specified signal to the container
  285. func (r *Runc) Kill(context context.Context, id string, sig int, opts *KillOpts) error {
  286. args := []string{
  287. "kill",
  288. }
  289. if opts != nil {
  290. args = append(args, opts.args()...)
  291. }
  292. return r.runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...))
  293. }
  294. // Stats return the stats for a container like cpu, memory, and io
  295. func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
  296. cmd := r.command(context, "events", "--stats", id)
  297. rd, err := cmd.StdoutPipe()
  298. if err != nil {
  299. return nil, err
  300. }
  301. ec, err := Monitor.Start(cmd)
  302. if err != nil {
  303. return nil, err
  304. }
  305. defer func() {
  306. rd.Close()
  307. Monitor.Wait(cmd, ec)
  308. }()
  309. var e Event
  310. if err := json.NewDecoder(rd).Decode(&e); err != nil {
  311. return nil, err
  312. }
  313. return e.Stats, nil
  314. }
  315. // Events returns an event stream from runc for a container with stats and OOM notifications
  316. func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
  317. cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
  318. rd, err := cmd.StdoutPipe()
  319. if err != nil {
  320. return nil, err
  321. }
  322. ec, err := Monitor.Start(cmd)
  323. if err != nil {
  324. rd.Close()
  325. return nil, err
  326. }
  327. var (
  328. dec = json.NewDecoder(rd)
  329. c = make(chan *Event, 128)
  330. )
  331. go func() {
  332. defer func() {
  333. close(c)
  334. rd.Close()
  335. Monitor.Wait(cmd, ec)
  336. }()
  337. for {
  338. var e Event
  339. if err := dec.Decode(&e); err != nil {
  340. if err == io.EOF {
  341. return
  342. }
  343. e = Event{
  344. Type: "error",
  345. Err: err,
  346. }
  347. }
  348. c <- &e
  349. }
  350. }()
  351. return c, nil
  352. }
  353. // Pause the container with the provided id
  354. func (r *Runc) Pause(context context.Context, id string) error {
  355. return r.runOrError(r.command(context, "pause", id))
  356. }
  357. // Resume the container with the provided id
  358. func (r *Runc) Resume(context context.Context, id string) error {
  359. return r.runOrError(r.command(context, "resume", id))
  360. }
  361. // Ps lists all the processes inside the container returning their pids
  362. func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
  363. data, err := cmdOutput(r.command(context, "ps", "--format", "json", id), true)
  364. if err != nil {
  365. return nil, fmt.Errorf("%s: %s", err, data)
  366. }
  367. var pids []int
  368. if err := json.Unmarshal(data, &pids); err != nil {
  369. return nil, err
  370. }
  371. return pids, nil
  372. }
  373. // Top lists all the processes inside the container returning the full ps data
  374. func (r *Runc) Top(context context.Context, id string, psOptions string) (*TopResults, error) {
  375. data, err := cmdOutput(r.command(context, "ps", "--format", "table", id, psOptions), true)
  376. if err != nil {
  377. return nil, fmt.Errorf("%s: %s", err, data)
  378. }
  379. topResults, err := ParsePSOutput(data)
  380. if err != nil {
  381. return nil, fmt.Errorf("%s: ", err)
  382. }
  383. return topResults, nil
  384. }
  385. type CheckpointOpts struct {
  386. // ImagePath is the path for saving the criu image file
  387. ImagePath string
  388. // WorkDir is the working directory for criu
  389. WorkDir string
  390. // ParentPath is the path for previous image files from a pre-dump
  391. ParentPath string
  392. // AllowOpenTCP allows open tcp connections to be checkpointed
  393. AllowOpenTCP bool
  394. // AllowExternalUnixSockets allows external unix sockets to be checkpointed
  395. AllowExternalUnixSockets bool
  396. // AllowTerminal allows the terminal(pty) to be checkpointed with a container
  397. AllowTerminal bool
  398. // CriuPageServer is the address:port for the criu page server
  399. CriuPageServer string
  400. // FileLocks handle file locks held by the container
  401. FileLocks bool
  402. // Cgroups is the cgroup mode for how to handle the checkpoint of a container's cgroups
  403. Cgroups CgroupMode
  404. // EmptyNamespaces creates a namespace for the container but does not save its properties
  405. // Provide the namespaces you wish to be checkpointed without their settings on restore
  406. EmptyNamespaces []string
  407. }
  408. type CgroupMode string
  409. const (
  410. Soft CgroupMode = "soft"
  411. Full CgroupMode = "full"
  412. Strict CgroupMode = "strict"
  413. )
  414. func (o *CheckpointOpts) args() (out []string) {
  415. if o.ImagePath != "" {
  416. out = append(out, "--image-path", o.ImagePath)
  417. }
  418. if o.WorkDir != "" {
  419. out = append(out, "--work-path", o.WorkDir)
  420. }
  421. if o.ParentPath != "" {
  422. out = append(out, "--parent-path", o.ParentPath)
  423. }
  424. if o.AllowOpenTCP {
  425. out = append(out, "--tcp-established")
  426. }
  427. if o.AllowExternalUnixSockets {
  428. out = append(out, "--ext-unix-sk")
  429. }
  430. if o.AllowTerminal {
  431. out = append(out, "--shell-job")
  432. }
  433. if o.CriuPageServer != "" {
  434. out = append(out, "--page-server", o.CriuPageServer)
  435. }
  436. if o.FileLocks {
  437. out = append(out, "--file-locks")
  438. }
  439. if string(o.Cgroups) != "" {
  440. out = append(out, "--manage-cgroups-mode", string(o.Cgroups))
  441. }
  442. for _, ns := range o.EmptyNamespaces {
  443. out = append(out, "--empty-ns", ns)
  444. }
  445. return out
  446. }
  447. type CheckpointAction func([]string) []string
  448. // LeaveRunning keeps the container running after the checkpoint has been completed
  449. func LeaveRunning(args []string) []string {
  450. return append(args, "--leave-running")
  451. }
  452. // PreDump allows a pre-dump of the checkpoint to be made and completed later
  453. func PreDump(args []string) []string {
  454. return append(args, "--pre-dump")
  455. }
  456. // Checkpoint allows you to checkpoint a container using criu
  457. func (r *Runc) Checkpoint(context context.Context, id string, opts *CheckpointOpts, actions ...CheckpointAction) error {
  458. args := []string{"checkpoint"}
  459. if opts != nil {
  460. args = append(args, opts.args()...)
  461. }
  462. for _, a := range actions {
  463. args = a(args)
  464. }
  465. return r.runOrError(r.command(context, append(args, id)...))
  466. }
  467. type RestoreOpts struct {
  468. CheckpointOpts
  469. IO
  470. Detach bool
  471. PidFile string
  472. NoSubreaper bool
  473. NoPivot bool
  474. ConsoleSocket ConsoleSocket
  475. }
  476. func (o *RestoreOpts) args() ([]string, error) {
  477. out := o.CheckpointOpts.args()
  478. if o.Detach {
  479. out = append(out, "--detach")
  480. }
  481. if o.PidFile != "" {
  482. abs, err := filepath.Abs(o.PidFile)
  483. if err != nil {
  484. return nil, err
  485. }
  486. out = append(out, "--pid-file", abs)
  487. }
  488. if o.ConsoleSocket != nil {
  489. out = append(out, "--console-socket", o.ConsoleSocket.Path())
  490. }
  491. if o.NoPivot {
  492. out = append(out, "--no-pivot")
  493. }
  494. if o.NoSubreaper {
  495. out = append(out, "-no-subreaper")
  496. }
  497. return out, nil
  498. }
  499. // Restore restores a container with the provide id from an existing checkpoint
  500. func (r *Runc) Restore(context context.Context, id, bundle string, opts *RestoreOpts) (int, error) {
  501. args := []string{"restore"}
  502. if opts != nil {
  503. oargs, err := opts.args()
  504. if err != nil {
  505. return -1, err
  506. }
  507. args = append(args, oargs...)
  508. }
  509. args = append(args, "--bundle", bundle)
  510. cmd := r.command(context, append(args, id)...)
  511. if opts != nil && opts.IO != nil {
  512. opts.Set(cmd)
  513. }
  514. ec, err := Monitor.Start(cmd)
  515. if err != nil {
  516. return -1, err
  517. }
  518. if opts != nil && opts.IO != nil {
  519. if c, ok := opts.IO.(StartCloser); ok {
  520. if err := c.CloseAfterStart(); err != nil {
  521. return -1, err
  522. }
  523. }
  524. }
  525. return Monitor.Wait(cmd, ec)
  526. }
  527. // Update updates the current container with the provided resource spec
  528. func (r *Runc) Update(context context.Context, id string, resources *specs.LinuxResources) error {
  529. buf := getBuf()
  530. defer putBuf(buf)
  531. if err := json.NewEncoder(buf).Encode(resources); err != nil {
  532. return err
  533. }
  534. args := []string{"update", "--resources", "-", id}
  535. cmd := r.command(context, args...)
  536. cmd.Stdin = buf
  537. return r.runOrError(cmd)
  538. }
  539. var ErrParseRuncVersion = errors.New("unable to parse runc version")
  540. type Version struct {
  541. Runc string
  542. Commit string
  543. Spec string
  544. }
  545. // Version returns the runc and runtime-spec versions
  546. func (r *Runc) Version(context context.Context) (Version, error) {
  547. data, err := cmdOutput(r.command(context, "--version"), false)
  548. if err != nil {
  549. return Version{}, err
  550. }
  551. return parseVersion(data)
  552. }
  553. func parseVersion(data []byte) (Version, error) {
  554. var v Version
  555. parts := strings.Split(strings.TrimSpace(string(data)), "\n")
  556. if len(parts) != 3 {
  557. return v, nil
  558. }
  559. for i, p := range []struct {
  560. dest *string
  561. split string
  562. }{
  563. {
  564. dest: &v.Runc,
  565. split: "version ",
  566. },
  567. {
  568. dest: &v.Commit,
  569. split: ": ",
  570. },
  571. {
  572. dest: &v.Spec,
  573. split: ": ",
  574. },
  575. } {
  576. p2 := strings.Split(parts[i], p.split)
  577. if len(p2) != 2 {
  578. return v, fmt.Errorf("unable to parse version line %q", parts[i])
  579. }
  580. *p.dest = p2[1]
  581. }
  582. return v, nil
  583. }
  584. func (r *Runc) args() (out []string) {
  585. if r.Root != "" {
  586. out = append(out, "--root", r.Root)
  587. }
  588. if r.Debug {
  589. out = append(out, "--debug")
  590. }
  591. if r.Log != "" {
  592. out = append(out, "--log", r.Log)
  593. }
  594. if r.LogFormat != none {
  595. out = append(out, "--log-format", string(r.LogFormat))
  596. }
  597. if r.Criu != "" {
  598. out = append(out, "--criu", r.Criu)
  599. }
  600. if r.SystemdCgroup {
  601. out = append(out, "--systemd-cgroup")
  602. }
  603. if r.Rootless != nil {
  604. // nil stands for "auto" (differs from explicit "false")
  605. out = append(out, "--rootless="+strconv.FormatBool(*r.Rootless))
  606. }
  607. return out
  608. }
  609. // runOrError will run the provided command. If an error is
  610. // encountered and neither Stdout or Stderr was set the error and the
  611. // stderr of the command will be returned in the format of <error>:
  612. // <stderr>
  613. func (r *Runc) runOrError(cmd *exec.Cmd) error {
  614. if cmd.Stdout != nil || cmd.Stderr != nil {
  615. ec, err := Monitor.Start(cmd)
  616. if err != nil {
  617. return err
  618. }
  619. status, err := Monitor.Wait(cmd, ec)
  620. if err == nil && status != 0 {
  621. err = fmt.Errorf("%s did not terminate sucessfully", cmd.Args[0])
  622. }
  623. return err
  624. }
  625. data, err := cmdOutput(cmd, true)
  626. if err != nil {
  627. return fmt.Errorf("%s: %s", err, data)
  628. }
  629. return nil
  630. }
  631. func cmdOutput(cmd *exec.Cmd, combined bool) ([]byte, error) {
  632. b := getBuf()
  633. defer putBuf(b)
  634. cmd.Stdout = b
  635. if combined {
  636. cmd.Stderr = b
  637. }
  638. ec, err := Monitor.Start(cmd)
  639. if err != nil {
  640. return nil, err
  641. }
  642. status, err := Monitor.Wait(cmd, ec)
  643. if err == nil && status != 0 {
  644. err = fmt.Errorf("%s did not terminate sucessfully", cmd.Args[0])
  645. }
  646. return b.Bytes(), err
  647. }