Skip to content

Commit 0aab392

Browse files
authored
Merge pull request #1914 from ktock/fuse-manager-tests
CI: test fuse manager
2 parents 7de6607 + 020d347 commit 0aab392

19 files changed

Lines changed: 369 additions & 125 deletions

File tree

.github/workflows/tests.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ jobs:
5656
builtin: ["true", "false"]
5757
metadata-store: ["memory", "db"]
5858
fuse-passthrough: ["true", "false"]
59+
fuse-manager: ["true", "false"]
5960
transfer-service: ["true", "false"]
6061
exclude:
6162
- buildargs: ""
@@ -70,6 +71,10 @@ jobs:
7071
buildargs: "--build-arg=CONTAINERD_VERSION=main"
7172
- fuse-passthrough: "true"
7273
metadata-store: "db"
74+
- fuse-manager: "true"
75+
builtin: "true"
76+
- fuse-manager: "true"
77+
buildargs: "--build-arg=CONTAINERD_VERSION=main"
7378
- transfer-service: "true"
7479
buildargs: "--build-arg=CONTAINERD_VERSION=main"
7580
- transfer-service: "true"
@@ -88,6 +93,7 @@ jobs:
8893
BUILTIN_SNAPSHOTTER: ${{ matrix.builtin }}
8994
METADATA_STORE: ${{ matrix.metadata-store }}
9095
FUSE_PASSTHROUGH: ${{ matrix.fuse-passthrough }}
96+
FUSE_MANAGER: ${{ matrix.fuse-manager }}
9197
TRANSFER_SERVICE: ${{ matrix.transfer-service }}
9298
run: make integration
9399

@@ -159,6 +165,7 @@ jobs:
159165
builtin: ["true", "false"]
160166
metadata-store: ["memory", "db"]
161167
fuse-passthrough: ["true", "false"]
168+
fuse-manager: ["true", "false"]
162169
transfer-service: ["true", "false"]
163170
exclude:
164171
- buildargs: ""
@@ -173,6 +180,10 @@ jobs:
173180
buildargs: "--build-arg=CONTAINERD_VERSION=main"
174181
- fuse-passthrough: "true"
175182
metadata-store: "db"
183+
- fuse-manager: "true"
184+
builtin: "true"
185+
- fuse-manager: "true"
186+
buildargs: "--build-arg=CONTAINERD_VERSION=main"
176187
- transfer-service: "true"
177188
buildargs: "--build-arg=CONTAINERD_VERSION=main"
178189
- transfer-service: "true"
@@ -189,6 +200,7 @@ jobs:
189200
BUILTIN_SNAPSHOTTER: ${{ matrix.builtin }}
190201
METADATA_STORE: ${{ matrix.metadata-store }}
191202
FUSE_PASSTHROUGH: ${{ matrix.fuse-passthrough }}
203+
FUSE_MANAGER: ${{ matrix.fuse-manager }}
192204
TRANSFER_SERVICE: ${{ matrix.transfer-service }}
193205
run: make test-cri-containerd
194206

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ COPY . $GOPATH/src/github.com/containerd/stargz-snapshotter
8888
ARG CGO_ENABLED
8989
RUN cd $GOPATH/src/github.com/containerd/stargz-snapshotter && \
9090
PREFIX=/out/ GOARCH=${TARGETARCH:-amd64} GO_BUILD_FLAGS=${SNAPSHOTTER_BUILD_FLAGS} make containerd-stargz-grpc && \
91-
PREFIX=/out/ GOARCH=${TARGETARCH:-amd64} GO_BUILD_FLAGS=${CTR_REMOTE_BUILD_FLAGS} make ctr-remote
91+
PREFIX=/out/ GOARCH=${TARGETARCH:-amd64} GO_BUILD_FLAGS=${CTR_REMOTE_BUILD_FLAGS} make ctr-remote && \
92+
PREFIX=/out/ GOARCH=${TARGETARCH:-amd64} GO_BUILD_FLAGS=${CTR_REMOTE_BUILD_FLAGS} make stargz-fuse-manager
9293

9394
# Build stargz store
9495
FROM golang-base AS stargz-store-dev

cmd/containerd-stargz-grpc/fsopts/fsopts.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
type Config struct {
3535
EnableIpfs bool
3636
MetadataStore string
37+
OpenBoltDB func(string) (*bolt.DB, error)
3738
}
3839

3940
const (
@@ -62,12 +63,10 @@ func getMetadataStore(rootDir string, config *Config) (metadata.Store, error) {
6263
case "", memoryMetadataType:
6364
return memorymetadata.NewReader, nil
6465
case dbMetadataType:
65-
bOpts := bolt.Options{
66-
NoFreelistSync: true,
67-
InitialMmapSize: 64 * 1024 * 1024,
68-
FreelistType: bolt.FreelistMapType,
66+
if config.OpenBoltDB == nil {
67+
return nil, fmt.Errorf("bolt DB is not configured")
6968
}
70-
db, err := bolt.Open(filepath.Join(rootDir, "metadata.db"), 0600, &bOpts)
69+
db, err := config.OpenBoltDB(filepath.Join(rootDir, "metadata.db"))
7170
if err != nil {
7271
return nil, err
7372
}

cmd/containerd-stargz-grpc/main.go

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
sddaemon "github.com/coreos/go-systemd/v22/daemon"
4545
metrics "github.com/docker/go-metrics"
4646
"github.com/pelletier/go-toml"
47+
bolt "go.etcd.io/bbolt"
4748
"golang.org/x/sys/unix"
4849
"google.golang.org/grpc"
4950
)
@@ -55,42 +56,48 @@ const (
5556
defaultRootDir = "/var/lib/containerd-stargz-grpc"
5657
defaultImageServiceAddress = "/run/containerd/containerd.sock"
5758
defaultFuseManagerAddress = "/run/containerd-stargz-grpc/fuse-manager.sock"
58-
59-
fuseManagerBin = "stargz-fuse-manager"
60-
fuseManagerAddress = "fuse-manager.sock"
59+
fuseManagerBin = "stargz-fuse-manager"
6160
)
6261

6362
var (
64-
address = flag.String("address", defaultAddress, "address for the snapshotter's GRPC server")
65-
configPath = flag.String("config", defaultConfigPath, "path to the configuration file")
66-
logLevel = flag.String("log-level", defaultLogLevel.String(), "set the logging level [trace, debug, info, warn, error, fatal, panic]")
67-
rootDir = flag.String("root", defaultRootDir, "path to the root directory for this snapshotter")
68-
detachFuseManager = flag.Bool("detach-fuse-manager", false, "whether detach fusemanager or not")
69-
printVersion = flag.Bool("version", false, "print the version")
63+
address = flag.String("address", defaultAddress, "address for the snapshotter's GRPC server")
64+
configPath = flag.String("config", defaultConfigPath, "path to the configuration file")
65+
logLevel = flag.String("log-level", defaultLogLevel.String(), "set the logging level [trace, debug, info, warn, error, fatal, panic]")
66+
rootDir = flag.String("root", defaultRootDir, "path to the root directory for this snapshotter")
67+
printVersion = flag.Bool("version", false, "print the version")
7068
)
7169

7270
type snapshotterConfig struct {
7371
service.Config
7472

7573
// MetricsAddress is address for the metrics API
76-
MetricsAddress string `toml:"metrics_address"`
74+
MetricsAddress string `toml:"metrics_address" json:"metrics_address"`
7775

7876
// NoPrometheus is a flag to disable the emission of the metrics
79-
NoPrometheus bool `toml:"no_prometheus"`
77+
NoPrometheus bool `toml:"no_prometheus" json:"no_prometheus"`
8078

8179
// DebugAddress is a Unix domain socket address where the snapshotter exposes /debug/ endpoints.
82-
DebugAddress string `toml:"debug_address"`
80+
DebugAddress string `toml:"debug_address" json:"debug_address"`
8381

8482
// IPFS is a flag to enbale lazy pulling from IPFS.
85-
IPFS bool `toml:"ipfs"`
83+
IPFS bool `toml:"ipfs" json:"ipfs"`
8684

8785
// MetadataStore is the type of the metadata store to use.
88-
MetadataStore string `toml:"metadata_store" default:"memory"`
89-
// FuseManagerAddress is address for the fusemanager's GRPC server
90-
FuseManagerAddress string `toml:"fusemanager_address"`
86+
MetadataStore string `toml:"metadata_store" default:"memory" json:"metadata_store"`
9187

92-
// FuseManagerPath is path to the fusemanager's executable
93-
FuseManagerPath string `toml:"fusemanager_path"`
88+
// FuseManagerConfig is configuration for fusemanager
89+
FuseManagerConfig `toml:"fuse_manager" json:"fuse_manager"`
90+
}
91+
92+
type FuseManagerConfig struct {
93+
// Enable is whether detach fusemanager or not
94+
Enable bool `toml:"enable" default:"false" json:"enable"`
95+
96+
// Address is address for the fusemanager's GRPC server (default: "/run/containerd-stargz-grpc/fuse-manager.sock")
97+
Address string `toml:"address" json:"address"`
98+
99+
// Path is path to the fusemanager's executable (default: looking for a binary "stargz-fuse-manager")
100+
Path string `toml:"path" json:"path"`
94101
}
95102

96103
func main() {
@@ -148,16 +155,17 @@ func main() {
148155
}
149156

150157
var rs snapshots.Snapshotter
151-
if *detachFuseManager {
152-
fmPath := config.FuseManagerPath
158+
fuseManagerConfig := config.FuseManagerConfig
159+
if fuseManagerConfig.Enable {
160+
fmPath := fuseManagerConfig.Path
153161
if fmPath == "" {
154162
var err error
155163
fmPath, err = exec.LookPath(fuseManagerBin)
156164
if err != nil {
157165
log.G(ctx).WithError(err).Fatalf("failed to find fusemanager bin")
158166
}
159167
}
160-
fmAddr := config.FuseManagerAddress
168+
fmAddr := fuseManagerConfig.Address
161169
if fmAddr == "" {
162170
fmAddr = defaultFuseManagerAddress
163171
}
@@ -171,7 +179,7 @@ func main() {
171179
}
172180

173181
fuseManagerConfig := fusemanager.Config{
174-
Config: &config.Config,
182+
Config: config.Config,
175183
IPFS: config.IPFS,
176184
MetadataStore: config.MetadataStore,
177185
DefaultImageServiceAddress: defaultImageServiceAddress,
@@ -195,14 +203,50 @@ func main() {
195203
}
196204
log.G(ctx).Infof("Start snapshotter with fusemanager mode")
197205
} else {
198-
credsFuncs, err := keychainconfig.ConfigKeychain(ctx, rpc, &keyChainConfig)
206+
crirpc := rpc
207+
// For CRI keychain, if listening path is different from stargz-snapshotter's socket, prepare for the dedicated grpc server and the socket.
208+
serveCRISocket := config.CRIKeychainConfig.EnableKeychain && config.ListenPath != "" && config.ListenPath != *address
209+
if serveCRISocket {
210+
crirpc = grpc.NewServer()
211+
}
212+
credsFuncs, err := keychainconfig.ConfigKeychain(ctx, crirpc, &keyChainConfig)
199213
if err != nil {
200214
log.G(ctx).WithError(err).Fatalf("failed to configure keychain")
201215
}
216+
if serveCRISocket {
217+
addr := config.ListenPath
218+
// Prepare the directory for the socket
219+
if err := os.MkdirAll(filepath.Dir(addr), 0700); err != nil {
220+
log.G(ctx).WithError(err).Fatalf("failed to create directory %q", filepath.Dir(addr))
221+
}
222+
223+
// Try to remove the socket file to avoid EADDRINUSE
224+
if err := os.RemoveAll(addr); err != nil {
225+
log.G(ctx).WithError(err).Fatalf("failed to remove %q", addr)
226+
}
227+
228+
// Listen and serve
229+
l, err := net.Listen("unix", addr)
230+
if err != nil {
231+
log.G(ctx).WithError(err).Fatalf("error on listen socket %q", addr)
232+
}
233+
go func() {
234+
if err := crirpc.Serve(l); err != nil {
235+
log.G(ctx).WithError(err).Errorf("error on serving CRI via socket %q", addr)
236+
}
237+
}()
238+
}
202239

203240
fsConfig := fsopts.Config{
204241
EnableIpfs: config.IPFS,
205242
MetadataStore: config.MetadataStore,
243+
OpenBoltDB: func(p string) (*bolt.DB, error) {
244+
return bolt.Open(p, 0600, &bolt.Options{
245+
NoFreelistSync: true,
246+
InitialMmapSize: 64 * 1024 * 1024,
247+
FreelistType: bolt.FreelistMapType,
248+
})
249+
},
206250
}
207251
fsOpts, err := fsopts.ConfigFsOpts(ctx, *rootDir, &fsConfig)
208252
if err != nil {
@@ -232,7 +276,7 @@ func main() {
232276
// commanded via SIGINT. The user can use SIGINT to gracefully killing the FUSE
233277
// manager before rebooting the node for ensuring that the all snapshots are
234278
// unmounted with cleaning up associated temporary resources.
235-
if cleanup || !*detachFuseManager {
279+
if cleanup || !fuseManagerConfig.Enable {
236280
log.G(ctx).Debug("Closing the snapshotter")
237281
rs.Close()
238282
}

cmd/stargz-fuse-manager/main.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,26 @@
1717
package main
1818

1919
import (
20+
"fmt"
21+
"net"
22+
"os"
23+
"path/filepath"
24+
25+
"github.com/containerd/log"
26+
2027
"github.com/containerd/stargz-snapshotter/cmd/containerd-stargz-grpc/fsopts"
2128
fusemanager "github.com/containerd/stargz-snapshotter/fusemanager"
2229
"github.com/containerd/stargz-snapshotter/service"
2330
"github.com/containerd/stargz-snapshotter/service/keychain/keychainconfig"
31+
"google.golang.org/grpc"
2432
)
2533

2634
func init() {
2735
fusemanager.RegisterConfigFunc(func(cc *fusemanager.ConfigContext) ([]service.Option, error) {
2836
fsConfig := fsopts.Config{
2937
EnableIpfs: cc.Config.IPFS,
3038
MetadataStore: cc.Config.MetadataStore,
39+
OpenBoltDB: cc.OpenBoltDB,
3140
}
3241
fsOpts, err := fsopts.ConfigFsOpts(cc.Ctx, cc.RootDir, &fsConfig)
3342
if err != nil {
@@ -44,10 +53,41 @@ func init() {
4453
DefaultImageServiceAddress: cc.Config.DefaultImageServiceAddress,
4554
ImageServicePath: cc.Config.Config.ImageServicePath,
4655
}
47-
credsFuncs, err := keychainconfig.ConfigKeychain(cc.Ctx, cc.Server, &keyChainConfig)
56+
if cc.Config.Config.CRIKeychainConfig.EnableKeychain && cc.Config.Config.ListenPath == "" || cc.Config.Config.ListenPath == cc.Address {
57+
return nil, fmt.Errorf("listen path of CRI server must be specified as a separated socket from FUSE manager server")
58+
}
59+
// For CRI keychain, if listening path is different from stargz-snapshotter's socket, prepare for the dedicated grpc server and the socket.
60+
serveCRISocket := cc.Config.Config.CRIKeychainConfig.EnableKeychain && cc.Config.Config.ListenPath != "" && cc.Config.Config.ListenPath != cc.Address
61+
if serveCRISocket {
62+
cc.CRIServer = grpc.NewServer()
63+
}
64+
credsFuncs, err := keychainconfig.ConfigKeychain(cc.Ctx, cc.CRIServer, &keyChainConfig)
4865
if err != nil {
4966
return nil, err
5067
}
68+
if serveCRISocket {
69+
addr := cc.Config.Config.ListenPath
70+
// Prepare the directory for the socket
71+
if err := os.MkdirAll(filepath.Dir(addr), 0700); err != nil {
72+
return nil, fmt.Errorf("failed to create directory %q: %w", filepath.Dir(addr), err)
73+
}
74+
75+
// Try to remove the socket file to avoid EADDRINUSE
76+
if err := os.RemoveAll(addr); err != nil {
77+
return nil, fmt.Errorf("failed to remove %q: %w", addr, err)
78+
}
79+
80+
// Listen and serve
81+
l, err := net.Listen("unix", addr)
82+
if err != nil {
83+
return nil, fmt.Errorf("error on listen socket %q: %w", addr, err)
84+
}
85+
go func() {
86+
if err := cc.CRIServer.Serve(l); err != nil {
87+
log.G(cc.Ctx).WithError(err).Errorf("error on serving CRI via socket %q", addr)
88+
}
89+
}()
90+
}
5191
return []service.Option{service.WithCredsFuncs(credsFuncs...)}, nil
5292
})
5393
}

docs/overview.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,12 @@ Remote snapshots are mounted using FUSE, and its filesystem processes are attach
108108

109109
To avoid this, we use a fuse daemon called the fuse manager to handle filesystem processes. The fuse manager is responsible for mounting and unmounting remote snapshotters. Its process is detached from the stargz snapshotter main process to an independent one in a shim-like way during the snapshotter's startup. This design ensures that the restart of the snapshotter won't affect the filesystem processes it manages, keeping mountpoints and running containers available during the restart. However, it is important to note that the restart of the fuse manager itself triggers a remount, so it is recommended to keep the fuse manager running in a good state.
110110

111-
You can enable the fuse manager by adding the flag `--detach-fuse-manager=true` to the stargz snapshotter.
111+
You can enable the fuse manager by adding the following configuration.
112+
113+
```toml
114+
[fusem_anager]
115+
enable = true
116+
```
112117

113118
## Killing and restarting Stargz Snapshotter
114119

@@ -173,13 +178,23 @@ The snapshotter acquires registry creds by scanning requests.
173178

174179
You must specify `--image-service-endpoint=unix:///run/containerd-stargz-grpc/containerd-stargz-grpc.sock` option to kubelet.
175180

181+
You can specify the backing image service's socket using `image_service_path`.
182+
The default is the containerd's socket (`/run/containerd/containerd.sock`).
183+
176184
```toml
177185
# Stargz Snapshotter proxies CRI Image Service into containerd socket.
178186
[cri_keychain]
179187
enable_keychain = true
180188
image_service_path = "/run/containerd/containerd.sock"
181189
```
182190

191+
The default path where containerd-stargz-grpc serves the CRI Image Service API is `unix:///run/containerd-stargz-grpc/containerd-stargz-grpc.sock`.
192+
You can also change this path using `listen_path` field.
193+
194+
> Note that if you enabled the FUSE manager and CRI-based authentication together, `listen_path` is a mandatory field with some caveats:
195+
> - This path must be different from the FUSE manager's socket path (`/run/containerd-stargz-grpc/fuse-manager.sock`) because they have different lifecycle. Specifically, the CRI socket is recreted on each reload of the configuration to the FUSE manager.
196+
> - containerd-stargz-grpc's socket path (`/run/containerd-stargz-grpc/containerd-stargz-grpc.sock`) can't be used as `listen_path` because the CRI socket is served by the FUSE manager process (not containerd-stargz-grpc process).
197+
183198
#### kubeconfig-based authentication
184199

185200
This is another way to enable lazy pulling of private images on Kubernetes.

0 commit comments

Comments
 (0)