 3cd8f9734d
			
		
	
	3cd8f9734d
	
	
	
		
			
			The Go runtime has started to [lock down future uses of linkname][1] since
go1.23. In the go source code, containerd project has been marked in the
comment, [hall of shame][2]. Well, the go:linkname is used to fork no-op
subprocess efficiently. However, since that comment, I would like to use
ptrace and remove go:linkname in the whole repository.
With go1.22 `go:linkname`:
```bash
$ go test -bench=.  -benchmem ./ -exec sudo
goos: linux
goarch: amd64
pkg: github.com/containerd/containerd/v2/core/mount
cpu: AMD Ryzen 7 5800H with Radeon Graphics
BenchmarkBatchRunGetUsernsFD_Concurrent1-16                 2440            533320 ns/op            1145 B/op         43 allocs/op
BenchmarkBatchRunGetUsernsFD_Concurrent10-16                 342           3661616 ns/op           11562 B/op        421 allocs/op
PASS
ok      github.com/containerd/containerd/v2/core/mount  2.983s
```
With go1.22 `ptrace`:
```bash
$ go test -bench=.  -benchmem ./ -exec sudo
goos: linux
goarch: amd64
pkg: github.com/containerd/containerd/v2/core/mount
cpu: AMD Ryzen 7 5800H with Radeon Graphics
BenchmarkBatchRunGetUsernsFD_Concurrent1-16                 1785            739557 ns/op            3948 B/op         68 allocs/op
BenchmarkBatchRunGetUsernsFD_Concurrent10-16                 328           4024300 ns/op           39601 B/op        671 allocs/op
PASS
ok      github.com/containerd/containerd/v2/core/mount  3.104s
```
With go1.23 `ptrace`:
```bash
$ go test -bench=.  -benchmem ./ -exec sudo
goos: linux
goarch: amd64
pkg: github.com/containerd/containerd/v2/core/mount
cpu: AMD Ryzen 7 5800H with Radeon Graphics
BenchmarkBatchRunGetUsernsFD_Concurrent1-16                 1815            723252 ns/op            4220 B/op         69 allocs/op
BenchmarkBatchRunGetUsernsFD_Concurrent10-16                 319           3957157 ns/op           42351 B/op        682 allocs/op
PASS
ok      github.com/containerd/containerd/v2/core/mount  3.051s
```
Diff:
The `ptrace` is slower than `go:linkname` mode. However, it's accepctable.
```
goos: linux
goarch: amd64
pkg: github.com/containerd/containerd/v2/core/mount
cpu: AMD Ryzen 7 5800H with Radeon Graphics
                                    │ go122-golinkname │             go122-ptrace              │             go123-ptrace              │
                                    │      sec/op      │    sec/op     vs base                 │    sec/op     vs base                 │
BatchRunGetUsernsFD_Concurrent1-16        533.3µ ± ∞ ¹   739.6µ ± ∞ ¹        ~ (p=1.000 n=1) ²   723.3µ ± ∞ ¹        ~ (p=1.000 n=1) ²
BatchRunGetUsernsFD_Concurrent10-16       3.662m ± ∞ ¹   4.024m ± ∞ ¹        ~ (p=1.000 n=1) ²   3.957m ± ∞ ¹        ~ (p=1.000 n=1) ²
geomean                                   1.397m         1.725m        +23.45%                   1.692m        +21.06%
¹ need >= 6 samples for confidence interval at level 0.95
² need >= 4 samples to detect a difference at alpha level 0.05
                                    │ go122-golinkname │              go122-ptrace               │              go123-ptrace               │
                                    │       B/op       │     B/op       vs base                  │     B/op       vs base                  │
BatchRunGetUsernsFD_Concurrent1-16       1.118Ki ± ∞ ¹   3.855Ki ± ∞ ¹         ~ (p=1.000 n=1) ²   4.121Ki ± ∞ ¹         ~ (p=1.000 n=1) ²
BatchRunGetUsernsFD_Concurrent10-16      11.29Ki ± ∞ ¹   38.67Ki ± ∞ ¹         ~ (p=1.000 n=1) ²   41.36Ki ± ∞ ¹         ~ (p=1.000 n=1) ²
geomean                                  3.553Ki         12.21Ki        +243.65%                   13.06Ki        +267.43%
¹ need >= 6 samples for confidence interval at level 0.95
² need >= 4 samples to detect a difference at alpha level 0.05
                                    │ go122-golinkname │             go122-ptrace             │             go123-ptrace             │
                                    │    allocs/op     │  allocs/op   vs base                 │  allocs/op   vs base                 │
BatchRunGetUsernsFD_Concurrent1-16         43.00 ± ∞ ¹   68.00 ± ∞ ¹        ~ (p=1.000 n=1) ²   69.00 ± ∞ ¹        ~ (p=1.000 n=1) ²
BatchRunGetUsernsFD_Concurrent10-16        421.0 ± ∞ ¹   671.0 ± ∞ ¹        ~ (p=1.000 n=1) ²   682.0 ± ∞ ¹        ~ (p=1.000 n=1) ²
geomean                                    134.5         213.6        +58.76%                   216.9        +61.23%
¹ need >= 6 samples for confidence interval at level 0.95
² need >= 4 samples to detect a difference at alpha level 0.05
```
[1]: <https://github.com/golang/go/issues/67401>
[2]: <https://github.com/golang/go/blob/release-branch.go1.23/src/runtime/proc.go#L4820>
Signed-off-by: Wei Fu <fuweid89@gmail.com>
		
	
		
			
				
	
	
		
			182 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			182 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|    Copyright The containerd Authors.
 | |
| 
 | |
|    Licensed under the Apache License, Version 2.0 (the "License");
 | |
|    you may not use this file except in compliance with the License.
 | |
|    You may obtain a copy of the License at
 | |
| 
 | |
|        http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
|    Unless required by applicable law or agreed to in writing, software
 | |
|    distributed under the License is distributed on an "AS IS" BASIS,
 | |
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|    See the License for the specific language governing permissions and
 | |
|    limitations under the License.
 | |
| */
 | |
| 
 | |
| package mount
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"sync"
 | |
| 	"syscall"
 | |
| 	"testing"
 | |
| 
 | |
| 	kernel "github.com/containerd/containerd/v2/pkg/kernelversion"
 | |
| 	"github.com/containerd/continuity/testutil"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| func BenchmarkBatchRunGetUsernsFD_Concurrent1(b *testing.B) {
 | |
| 	for range b.N {
 | |
| 		benchmarkBatchRunGetUsernsFD(1)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func BenchmarkBatchRunGetUsernsFD_Concurrent10(b *testing.B) {
 | |
| 	for range b.N {
 | |
| 		benchmarkBatchRunGetUsernsFD(10)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func benchmarkBatchRunGetUsernsFD(n int) {
 | |
| 	var wg sync.WaitGroup
 | |
| 	wg.Add(n)
 | |
| 	for i := 0; i < n; i++ {
 | |
| 		go func() {
 | |
| 			defer wg.Done()
 | |
| 			fd, err := getUsernsFD(testUIDMaps, testGIDMaps)
 | |
| 			if err != nil {
 | |
| 				panic(err)
 | |
| 			}
 | |
| 			fd.Close()
 | |
| 		}()
 | |
| 	}
 | |
| 	wg.Wait()
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	testUIDMaps = []syscall.SysProcIDMap{
 | |
| 		{ContainerID: 1000, HostID: 0, Size: 100},
 | |
| 		{ContainerID: 5000, HostID: 2000, Size: 100},
 | |
| 		{ContainerID: 10000, HostID: 3000, Size: 100},
 | |
| 	}
 | |
| 
 | |
| 	testGIDMaps = []syscall.SysProcIDMap{
 | |
| 		{ContainerID: 1000, HostID: 0, Size: 100},
 | |
| 		{ContainerID: 5000, HostID: 2000, Size: 100},
 | |
| 		{ContainerID: 10000, HostID: 3000, Size: 100},
 | |
| 	}
 | |
| )
 | |
| 
 | |
| func TestIdmappedMount(t *testing.T) {
 | |
| 	testutil.RequiresRoot(t)
 | |
| 
 | |
| 	k512 := kernel.KernelVersion{Kernel: 5, Major: 12}
 | |
| 	ok, err := kernel.GreaterEqualThan(k512)
 | |
| 	require.NoError(t, err)
 | |
| 	if !ok {
 | |
| 		t.Skip("GetUsernsFD requires kernel >= 5.12")
 | |
| 	}
 | |
| 
 | |
| 	t.Run("GetUsernsFD", testGetUsernsFD)
 | |
| 
 | |
| 	t.Run("IDMapMount", testIDMapMount)
 | |
| }
 | |
| 
 | |
| func testGetUsernsFD(t *testing.T) {
 | |
| 	for idx, tc := range []struct {
 | |
| 		uidMaps string
 | |
| 		gidMaps string
 | |
| 		hasErr  bool
 | |
| 	}{
 | |
| 		{
 | |
| 			uidMaps: "0:1000:100",
 | |
| 			gidMaps: "0:1000:100",
 | |
| 			hasErr:  false,
 | |
| 		},
 | |
| 		{
 | |
| 			uidMaps: "100:1000:100",
 | |
| 			gidMaps: "0:-1:100",
 | |
| 			hasErr:  true,
 | |
| 		},
 | |
| 		{
 | |
| 			uidMaps: "100:1000:100",
 | |
| 			gidMaps: "-1:1000:100",
 | |
| 			hasErr:  true,
 | |
| 		},
 | |
| 		{
 | |
| 			uidMaps: "100:1000:100",
 | |
| 			gidMaps: "0:1000:-1",
 | |
| 			hasErr:  true,
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Run(fmt.Sprintf("#%v", idx), func(t *testing.T) {
 | |
| 			_, err := GetUsernsFD(tc.uidMaps, tc.gidMaps)
 | |
| 			if tc.hasErr {
 | |
| 				require.Error(t, err)
 | |
| 			} else {
 | |
| 				require.NoError(t, err)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testIDMapMount(t *testing.T) {
 | |
| 	usernsFD, err := getUsernsFD(testUIDMaps, testGIDMaps)
 | |
| 	require.NoError(t, err)
 | |
| 	defer usernsFD.Close()
 | |
| 
 | |
| 	srcDir, checkFunc := initIDMappedChecker(t, testUIDMaps, testGIDMaps)
 | |
| 	destDir := t.TempDir()
 | |
| 	defer func() {
 | |
| 		require.NoError(t, UnmountAll(destDir, 0))
 | |
| 	}()
 | |
| 
 | |
| 	err = IDMapMount(srcDir, destDir, int(usernsFD.Fd()))
 | |
| 	usernsFD.Close()
 | |
| 	require.NoError(t, err)
 | |
| 	checkFunc(destDir)
 | |
| }
 | |
| 
 | |
| func initIDMappedChecker(t *testing.T, uidMaps, gidMaps []syscall.SysProcIDMap) (_srcDir string, _verifyFunc func(destDir string)) {
 | |
| 	testutil.RequiresRoot(t)
 | |
| 
 | |
| 	srcDir := t.TempDir()
 | |
| 
 | |
| 	require.Equal(t, len(uidMaps), len(gidMaps))
 | |
| 	for idx := range uidMaps {
 | |
| 		file := filepath.Join(srcDir, fmt.Sprintf("%v", idx))
 | |
| 
 | |
| 		f, err := os.Create(file)
 | |
| 		require.NoError(t, err, fmt.Sprintf("create file %s", file))
 | |
| 		defer f.Close()
 | |
| 
 | |
| 		uid, gid := uidMaps[idx].ContainerID, gidMaps[idx].ContainerID
 | |
| 		err = f.Chown(uid, gid)
 | |
| 		require.NoError(t, err, fmt.Sprintf("chown %v:%v for file %s", uid, gid, file))
 | |
| 	}
 | |
| 
 | |
| 	return srcDir, func(destDir string) {
 | |
| 		for idx := range uidMaps {
 | |
| 			file := filepath.Join(destDir, fmt.Sprintf("%v", idx))
 | |
| 
 | |
| 			f, err := os.Open(file)
 | |
| 			require.NoError(t, err, fmt.Sprintf("open file %s", file))
 | |
| 			defer f.Close()
 | |
| 
 | |
| 			stat, err := f.Stat()
 | |
| 			require.NoError(t, err, fmt.Sprintf("stat file %s", file))
 | |
| 
 | |
| 			sysStat := stat.Sys().(*syscall.Stat_t)
 | |
| 
 | |
| 			uid, gid := uidMaps[idx].HostID, gidMaps[idx].HostID
 | |
| 			require.Equal(t, uint32(uid), sysStat.Uid, fmt.Sprintf("check file %s uid", file))
 | |
| 			require.Equal(t, uint32(gid), sysStat.Gid, fmt.Sprintf("check file %s gid", file))
 | |
| 			t.Logf("IDMapped File %s uid=%v, gid=%v", file, uid, gid)
 | |
| 		}
 | |
| 	}
 | |
| }
 |