Skip to content

Commit bba26f8

Browse files
committed
Add vminitd for running services inside the vm
Signed-off-by: Derek McGowan <derek@mcg.dev>
1 parent d3c85ce commit bba26f8

File tree

1 file changed

+279
-0
lines changed

1 file changed

+279
-0
lines changed

cmd/vminitd/main.go

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"flag"
22+
"fmt"
23+
"net"
24+
"os"
25+
"path/filepath"
26+
"time"
27+
28+
"github.com/containerd/containerd/pkg/shutdown"
29+
"github.com/containerd/containerd/v2/core/mount"
30+
"github.com/containerd/containerd/v2/plugins"
31+
"github.com/containerd/log"
32+
"github.com/containerd/otelttrpc"
33+
"github.com/containerd/plugin"
34+
"github.com/containerd/plugin/registry"
35+
"github.com/containerd/ttrpc"
36+
"github.com/mdlayher/vsock"
37+
38+
_ "github.com/containerd/containerd/v2/plugins/events"
39+
40+
_ "github.com/dmcgowan/nerdbox/plugins/services/system"
41+
_ "github.com/dmcgowan/nerdbox/plugins/vminit/task"
42+
)
43+
44+
func main() {
45+
t1 := time.Now()
46+
var (
47+
config ServiceConfig
48+
dev = flag.Bool("dev", false, "Development mode with graceful exit")
49+
debug = flag.Bool("debug", true, "Debug log level")
50+
)
51+
flag.IntVar(&config.VSockPort, "vsock-port", 1024, "vsock port to listen on")
52+
flag.IntVar(&config.VSockContextID, "vsock-cid", 0, "vsock context ID for vsock listen")
53+
54+
args := os.Args[1:]
55+
// Strip "tsi_hijack" added by libkrun
56+
if len(args) > 0 && args[0] == "tsi_hijack" {
57+
args = args[1:]
58+
}
59+
flag.CommandLine.Parse(args)
60+
flag.Parse()
61+
62+
if *dev || *debug {
63+
log.SetLevel("debug")
64+
}
65+
66+
ctx := context.Background()
67+
68+
log.G(ctx).WithField("args", args).WithField("env", os.Environ()).Debug("starting vminitd")
69+
70+
var err error
71+
defer func() {
72+
if err != nil {
73+
log.G(ctx).WithError(err).Error("exiting with error")
74+
//} else if p := recover(); p != nil {
75+
// log.G(ctx).WithField("panic", p).Error("recovered from panic")
76+
} else {
77+
log.G(ctx).Debug("exiting cleanly")
78+
}
79+
80+
if !*dev {
81+
// Exit method is based on VM manager
82+
// For krun, just os.Exit(0) is sufficient
83+
84+
log.G(ctx).Debug("poweroff")
85+
//unix.Reboot(unix.LINUX_REBOOT_CMD_POWER_OFF)
86+
//os.Exit(1)
87+
//unix.Kill(1, unix.SIGTERM) // Send SIGTERM to init process
88+
}
89+
}()
90+
91+
if err = systemInit(); err != nil {
92+
return
93+
}
94+
95+
if *debug {
96+
filepath.Walk("/", func(path string, info os.FileInfo, err error) error {
97+
if path == "/proc" || path == "/sys" {
98+
path = fmt.Sprintf("%s (skipping)", path)
99+
err = filepath.SkipDir
100+
}
101+
102+
if info != nil {
103+
log.G(ctx).WithFields(
104+
log.Fields{
105+
"mode": info.Mode(),
106+
"size": info.Size(),
107+
}).Debug(path)
108+
}
109+
110+
return err
111+
})
112+
113+
b, err := os.ReadFile("/proc/cmdline")
114+
if err != nil {
115+
return
116+
}
117+
log.G(ctx).WithField("cmdline", string(b)).Debug("kernel command line")
118+
}
119+
120+
ctx, config.Shutdown = shutdown.WithShutdown(ctx)
121+
defer config.Shutdown.Shutdown()
122+
123+
service, err := New(ctx, config)
124+
if err != nil {
125+
return
126+
}
127+
128+
log.G(ctx).WithField("t", time.Since(t1)).Debug("initialized vminitd")
129+
130+
err = service.Run(ctx)
131+
}
132+
133+
func systemInit() error {
134+
if err := systemMounts(); err != nil {
135+
return err
136+
}
137+
return setupCgroupControl()
138+
}
139+
140+
func systemMounts() error {
141+
return mount.All([]mount.Mount{
142+
{
143+
Type: "proc",
144+
Source: "proc",
145+
Target: "/proc",
146+
},
147+
{
148+
Type: "sysfs",
149+
Source: "sysfs",
150+
Target: "/sys",
151+
},
152+
{
153+
Type: "cgroup2",
154+
Source: "none",
155+
Target: "/sys/fs/cgroup",
156+
},
157+
{
158+
Type: "tmpfs",
159+
Source: "tmpfs",
160+
Target: "/run",
161+
},
162+
{
163+
Type: "tmpfs",
164+
Source: "tmpsfs",
165+
Target: "/tmp",
166+
},
167+
}, "/")
168+
}
169+
170+
func setupCgroupControl() error {
171+
return os.WriteFile("/sys/fs/cgroup/cgroup.subtree_control", []byte("+cpu +cpuset +io +memory +pids"), 0644)
172+
}
173+
174+
// ttrpcService allows TTRPC services to be registered with the underlying server
175+
type ttrpcService interface {
176+
RegisterTTRPC(*ttrpc.Server) error
177+
}
178+
179+
type service struct {
180+
l net.Listener
181+
server *ttrpc.Server
182+
}
183+
184+
type Runnable interface {
185+
Run(context.Context) error
186+
}
187+
188+
type ServiceConfig struct {
189+
VSockContextID int
190+
VSockPort int
191+
Shutdown shutdown.Service
192+
}
193+
194+
func New(ctx context.Context, config ServiceConfig) (Runnable, error) {
195+
var (
196+
initializedPlugins = plugin.NewPluginSet()
197+
pluginProperties = map[string]string{}
198+
disabledPlugins = map[string]struct{}{}
199+
// TODO: service config?
200+
)
201+
202+
l, err := vsock.ListenContextID(uint32(config.VSockContextID), uint32(config.VSockPort), &vsock.Config{})
203+
if err != nil {
204+
return nil, fmt.Errorf("failed to listen on vsock port %d with context id %d: %w", config.VSockPort, config.VSockContextID, err)
205+
}
206+
ts, err := ttrpc.NewServer(
207+
ttrpc.WithUnaryServerInterceptor(otelttrpc.UnaryServerInterceptor()),
208+
)
209+
if err != nil {
210+
return nil, err
211+
}
212+
213+
registry.Register(&plugin.Registration{
214+
Type: plugins.InternalPlugin,
215+
ID: "shutdown",
216+
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
217+
return config.Shutdown, nil
218+
},
219+
})
220+
221+
// TODO: Allow disabling plugins
222+
//if len(cfg.DisabledPlugins) > 0 {
223+
// for _, p := range cfg.DisabledPlugins {
224+
// disabledPlugins[p] = struct{}{}
225+
// }
226+
//}
227+
228+
for _, reg := range registry.Graph(func(*plugin.Registration) bool { return false }) {
229+
id := reg.URI()
230+
if _, ok := disabledPlugins[id]; ok {
231+
log.G(ctx).WithField("plugin_id", id).Info("plugin is disabled, skipping load")
232+
continue
233+
}
234+
235+
log.G(ctx).WithField("plugin_id", id).Info("loading plugin")
236+
237+
ic := plugin.NewContext(ctx, initializedPlugins, pluginProperties)
238+
239+
if reg.Config != nil {
240+
// TODO: Allow plugin config?
241+
ic.Config = reg.Config
242+
}
243+
244+
p := reg.Init(ic)
245+
if err := initializedPlugins.Add(p); err != nil {
246+
return nil, fmt.Errorf("could not add plugin result to plugin set: %w", err)
247+
}
248+
249+
instance, err := p.Instance()
250+
if err != nil {
251+
if plugin.IsSkipPlugin(err) {
252+
log.G(ctx).WithFields(log.Fields{"error": err, "plugin_id": id}).Info("skip loading plugin")
253+
continue
254+
}
255+
256+
return nil, fmt.Errorf("failed to load plugin %s: %w", id, err)
257+
}
258+
259+
if s, ok := instance.(ttrpcService); ok {
260+
s.RegisterTTRPC(ts)
261+
}
262+
}
263+
264+
return &service{
265+
l: l,
266+
server: ts,
267+
}, nil
268+
}
269+
270+
func (s *service) Run(ctx context.Context) error {
271+
s.server.Serve(ctx, s.l)
272+
return nil
273+
}
274+
275+
func newTTRPCServer() (*ttrpc.Server, error) {
276+
return ttrpc.NewServer(
277+
ttrpc.WithUnaryServerInterceptor(otelttrpc.UnaryServerInterceptor()),
278+
)
279+
}

0 commit comments

Comments
 (0)