Skip to content

Commit 66a1ba0

Browse files
authored
Get more insights on workspace image (#20356)
* 1 * 1 * 1 * add timeout ctx * addressed feedback
1 parent f7a95c2 commit 66a1ba0

File tree

18 files changed

+274
-107
lines changed

18 files changed

+274
-107
lines changed

components/registry-facade/pkg/registry/manifest.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"io"
1313
"mime"
1414
"net/http"
15+
"strconv"
1516
"strings"
1617
"time"
1718

@@ -185,13 +186,23 @@ func (mh *manifestHandler) getManifest(w http.ResponseWriter, r *http.Request) {
185186
return err
186187
}
187188

189+
originImageSize := 0
190+
for _, layer := range manifest.Layers {
191+
originImageSize += int(layer.Size)
192+
}
193+
188194
// modify config
189195
addonLayer, err := mh.ConfigModifier(ctx, mh.Spec, cfg)
190196
if err != nil {
191197
log.WithError(err).WithFields(logFields).Error("cannot modify config")
192198
return err
193199
}
194200
manifest.Layers = append(manifest.Layers, addonLayer...)
201+
if manifest.Annotations == nil {
202+
manifest.Annotations = make(map[string]string)
203+
}
204+
manifest.Annotations["io.gitpod.workspace-image.size"] = strconv.Itoa(originImageSize)
205+
manifest.Annotations["io.gitpod.workspace-image.ref"] = mh.Spec.BaseRef
195206

196207
// place config in store
197208
rawCfg, err := json.Marshal(cfg)

components/ws-daemon/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ require (
8282
github.com/containerd/continuity v0.4.2 // indirect
8383
github.com/containerd/fifo v1.1.0 // indirect
8484
github.com/containerd/log v0.1.0 // indirect
85+
github.com/containerd/platforms v0.2.1 // indirect
8586
github.com/containerd/ttrpc v1.2.2 // indirect
8687
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
8788
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
@@ -144,7 +145,7 @@ require (
144145
github.com/modern-go/reflect2 v1.0.2 // indirect
145146
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
146147
github.com/opencontainers/go-digest v1.0.0 // indirect
147-
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
148+
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
148149
github.com/opencontainers/selinux v1.11.0 // indirect
149150
github.com/pkg/errors v0.9.1 // indirect
150151
github.com/pmezard/go-difflib v1.0.0 // indirect

components/ws-daemon/go.sum

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/ws-daemon/pkg/container/container.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"context"
99

1010
"golang.org/x/xerrors"
11+
12+
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
1113
)
1214

1315
// Runtime abstracts over the different container runtimes out there w.r.t. to the features we need from those runtimes
@@ -47,6 +49,8 @@ type Runtime interface {
4749

4850
// IsContainerdReady returns is the status of containerd.
4951
IsContainerdReady(ctx context.Context) (bool, error)
52+
53+
GetContainerImageInfo(ctx context.Context, id ID) (*workspacev1.WorkspaceImageInfo, error)
5054
}
5155

5256
var (

components/ws-daemon/pkg/container/containerd.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"path/filepath"
1212
"regexp"
13+
"strconv"
1314
"strings"
1415
"sync"
1516
"time"
@@ -20,6 +21,8 @@ import (
2021
"github.com/containerd/containerd/api/types"
2122
"github.com/containerd/containerd/containers"
2223
"github.com/containerd/containerd/errdefs"
24+
"github.com/containerd/containerd/images"
25+
"github.com/containerd/platforms"
2326
"github.com/containerd/typeurl/v2"
2427
ocispecs "github.com/opencontainers/runtime-spec/specs-go"
2528
"github.com/opentracing/opentracing-go"
@@ -28,6 +31,7 @@ import (
2831
wsk8s "github.com/gitpod-io/gitpod/common-go/kubernetes"
2932
"github.com/gitpod-io/gitpod/common-go/log"
3033
"github.com/gitpod-io/gitpod/common-go/tracing"
34+
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
3135
)
3236

3337
const (
@@ -92,6 +96,7 @@ type containerInfo struct {
9296
UpperDir string
9397
CGroupPath string
9498
PID uint32
99+
ImageRef string
95100
}
96101

97102
// start listening to containerd
@@ -276,6 +281,7 @@ func (s *Containerd) handleNewContainer(c containers.Container) {
276281
info.ID = c.ID
277282
info.SnapshotKey = c.SnapshotKey
278283
info.Snapshotter = c.Snapshotter
284+
info.ImageRef = c.Image
279285

280286
s.cntIdx[c.ID] = info
281287
log.WithField("podname", podName).WithFields(log.OWI(info.OwnerID, info.WorkspaceID, info.InstanceID)).WithField("ID", c.ID).Debug("found workspace container - updating label cache")
@@ -480,6 +486,40 @@ func (s *Containerd) ContainerPID(ctx context.Context, id ID) (pid uint64, err e
480486
return uint64(info.PID), nil
481487
}
482488

489+
func (s *Containerd) GetContainerImageInfo(ctx context.Context, id ID) (*workspacev1.WorkspaceImageInfo, error) {
490+
info, ok := s.cntIdx[string(id)]
491+
if !ok {
492+
return nil, ErrNotFound
493+
}
494+
495+
image, err := s.Client.GetImage(ctx, info.ImageRef)
496+
if err != nil {
497+
return nil, err
498+
}
499+
size, err := image.Size(ctx)
500+
if err != nil {
501+
return nil, err
502+
}
503+
504+
wsImageInfo := &workspacev1.WorkspaceImageInfo{
505+
TotalSize: size,
506+
}
507+
508+
// Fetch the manifest
509+
manifest, err := images.Manifest(ctx, s.Client.ContentStore(), image.Target(), platforms.Default())
510+
if err != nil {
511+
log.WithError(err).WithField("image", info.ImageRef).Error("Failed to get manifest")
512+
return wsImageInfo, nil
513+
}
514+
if manifest.Annotations != nil {
515+
wsImageInfo.WorkspaceImageRef = manifest.Annotations["io.gitpod.workspace-image.ref"]
516+
if size, err := strconv.Atoi(manifest.Annotations["io.gitpod.workspace-image.size"]); err == nil {
517+
wsImageInfo.WorkspaceImageSize = int64(size)
518+
}
519+
}
520+
return wsImageInfo, nil
521+
}
522+
483523
func (s *Containerd) IsContainerdReady(ctx context.Context) (bool, error) {
484524
if len(s.registryFacadeHost) == 0 {
485525
return s.Client.IsServing(ctx)

components/ws-daemon/pkg/controller/mock.go

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/ws-daemon/pkg/controller/suite_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ var _ = BeforeSuite(func() {
7777
Expect(err).ToNot(HaveOccurred())
7878
ctx, cancel = context.WithCancel(context.Background())
7979

80-
workspaceCtrl, err = NewWorkspaceController(k8sClient, record.NewFakeRecorder(100), NodeName, secretsNamespace, 5, nil, ctrl_metrics.Registry)
80+
workspaceCtrl, err = NewWorkspaceController(k8sClient, record.NewFakeRecorder(100), NodeName, secretsNamespace, 5, nil, ctrl_metrics.Registry, nil)
8181
Expect(err).NotTo(HaveOccurred())
8282

8383
Expect(workspaceCtrl.SetupWithManager(k8sManager)).To(Succeed())

components/ws-daemon/pkg/controller/workspace_controller.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,10 @@ type WorkspaceController struct {
6161
metrics *workspaceMetrics
6262
secretNamespace string
6363
recorder record.EventRecorder
64+
runtime container.Runtime
6465
}
6566

66-
func NewWorkspaceController(c client.Client, recorder record.EventRecorder, nodeName, secretNamespace string, maxConcurrentReconciles int, ops WorkspaceOperations, reg prometheus.Registerer) (*WorkspaceController, error) {
67+
func NewWorkspaceController(c client.Client, recorder record.EventRecorder, nodeName, secretNamespace string, maxConcurrentReconciles int, ops WorkspaceOperations, reg prometheus.Registerer, runtime container.Runtime) (*WorkspaceController, error) {
6768
metrics := newWorkspaceMetrics()
6869
reg.Register(metrics)
6970

@@ -75,6 +76,7 @@ func NewWorkspaceController(c client.Client, recorder record.EventRecorder, node
7576
metrics: metrics,
7677
secretNamespace: secretNamespace,
7778
recorder: recorder,
79+
runtime: runtime,
7880
}, nil
7981
}
8082

@@ -219,7 +221,40 @@ func (wsc *WorkspaceController) handleWorkspaceRunning(ctx context.Context, ws *
219221
span, ctx := opentracing.StartSpanFromContext(ctx, "handleWorkspaceRunning")
220222
defer tracing.FinishSpan(span, &err)
221223

222-
return ctrl.Result{}, wsc.operations.SetupWorkspace(ctx, ws.Name)
224+
var imageInfo *workspacev1.WorkspaceImageInfo = nil
225+
if ws.Status.ImageInfo == nil {
226+
getImageInfo := func() (*workspacev1.WorkspaceImageInfo, error) {
227+
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
228+
defer cancel()
229+
id, err := wsc.runtime.WaitForContainer(ctx, ws.Name)
230+
if err != nil {
231+
return nil, fmt.Errorf("failed to wait for container: %w", err)
232+
}
233+
info, err := wsc.runtime.GetContainerImageInfo(ctx, id)
234+
if err != nil {
235+
return nil, fmt.Errorf("failed to get container image info: %w", err)
236+
}
237+
238+
err = retry.RetryOnConflict(retryParams, func() error {
239+
if err := wsc.Get(ctx, req.NamespacedName, ws); err != nil {
240+
return err
241+
}
242+
ws.Status.ImageInfo = info
243+
return wsc.Status().Update(ctx, ws)
244+
})
245+
if err != nil {
246+
return info, fmt.Errorf("failed to update workspace with image info: %w", err)
247+
}
248+
return info, nil
249+
}
250+
imageInfo, err = getImageInfo()
251+
if err != nil {
252+
glog.WithFields(ws.OWI()).WithField("workspace", req.NamespacedName).Errorf("failed to get image info: %v", err)
253+
} else {
254+
glog.WithFields(ws.OWI()).WithField("workspace", req.NamespacedName).WithField("imageInfo", glog.TrustedValueWrap{Value: imageInfo}).Info("updated image info")
255+
}
256+
}
257+
return ctrl.Result{}, wsc.operations.SetupWorkspace(ctx, ws.Name, imageInfo)
223258
}
224259

225260
func (wsc *WorkspaceController) handleWorkspaceStop(ctx context.Context, ws *workspacev1.Workspace, req ctrl.Request) (result ctrl.Result, err error) {

components/ws-daemon/pkg/controller/workspace_operations.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package controller
66

77
import (
88
"context"
9+
"encoding/json"
910
"errors"
1011
"fmt"
1112
"io/fs"
@@ -22,6 +23,7 @@ import (
2223
"github.com/gitpod-io/gitpod/content-service/pkg/storage"
2324
"github.com/gitpod-io/gitpod/ws-daemon/pkg/content"
2425
"github.com/gitpod-io/gitpod/ws-daemon/pkg/internal/session"
26+
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
2527
"github.com/opentracing/opentracing-go"
2628
"github.com/prometheus/client_golang/prometheus"
2729
"github.com/sirupsen/logrus"
@@ -71,7 +73,7 @@ type WorkspaceOperations interface {
7173
// Snapshot takes a snapshot of the workspace
7274
Snapshot(ctx context.Context, instanceID, snapshotName string) (err error)
7375
// Setup ensures that the workspace has been setup
74-
SetupWorkspace(ctx context.Context, instanceID string) error
76+
SetupWorkspace(ctx context.Context, instanceID string, imageInfo *workspacev1.WorkspaceImageInfo) error
7577
}
7678

7779
type DefaultWorkspaceOperations struct {
@@ -211,12 +213,15 @@ func (wso *DefaultWorkspaceOperations) creator(owner, workspaceID, instanceID st
211213
}
212214
}
213215

214-
func (wso *DefaultWorkspaceOperations) SetupWorkspace(ctx context.Context, instanceID string) error {
215-
_, err := wso.provider.GetAndConnect(ctx, instanceID)
216+
func (wso *DefaultWorkspaceOperations) SetupWorkspace(ctx context.Context, instanceID string, imageInfo *workspacev1.WorkspaceImageInfo) error {
217+
ws, err := wso.provider.GetAndConnect(ctx, instanceID)
216218
if err != nil {
217219
return fmt.Errorf("cannot setup workspace %s: %w", instanceID, err)
218220
}
219-
221+
err = wso.writeImageInfo(ctx, ws, imageInfo)
222+
if err != nil {
223+
glog.WithError(err).WithFields(ws.OWI()).Error("cannot write image info")
224+
}
220225
return nil
221226
}
222227

@@ -513,6 +518,26 @@ func (wso *DefaultWorkspaceOperations) uploadWorkspaceContent(ctx context.Contex
513518
return nil
514519
}
515520

521+
func (wso *DefaultWorkspaceOperations) writeImageInfo(_ context.Context, ws *session.Workspace, imageInfo *workspacev1.WorkspaceImageInfo) error {
522+
if imageInfo == nil {
523+
return nil
524+
}
525+
526+
b, err := json.Marshal(imageInfo)
527+
if err != nil {
528+
return fmt.Errorf("cannot marshal image info: %w", err)
529+
}
530+
uid := (wsinit.GitpodUID + 100000 - 1)
531+
gid := (wsinit.GitpodGID + 100000 - 1)
532+
fp := filepath.Join(ws.Location, ".gitpod/image")
533+
err = os.WriteFile(fp, b, 0644)
534+
if err != nil {
535+
return fmt.Errorf("cannot write image info: %w", err)
536+
}
537+
os.Chown(fp, uid, gid)
538+
return nil
539+
}
540+
516541
func retryIfErr(ctx context.Context, attempts int, log *logrus.Entry, op func(ctx context.Context) error) (err error) {
517542
//nolint:ineffassign
518543
span, ctx := opentracing.StartSpanFromContext(ctx, "retryIfErr")

components/ws-daemon/pkg/daemon/daemon.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ func NewDaemon(config Config) (*Daemon, error) {
214214
}
215215

216216
wsctrl, err := controller.NewWorkspaceController(
217-
mgr.GetClient(), mgr.GetEventRecorderFor("workspace"), nodename, config.Runtime.SecretsNamespace, config.WorkspaceController.MaxConcurrentReconciles, workspaceOps, wrappedReg)
217+
mgr.GetClient(), mgr.GetEventRecorderFor("workspace"), nodename, config.Runtime.SecretsNamespace, config.WorkspaceController.MaxConcurrentReconciles, workspaceOps, wrappedReg, containerRuntime)
218218
if err != nil {
219219
return nil, err
220220
}

0 commit comments

Comments
 (0)