Golang 및 WebAssembly의 첫 번째 단계

지난 몇 주 동안 저는 Kubernetes를 위한 새로운 오픈 소스 선언적 볼륨 구성 솔루션인 Discoblock 작업을 했습니다. 자세한 내용은 내 . 따라서 세부 사항으로 들어가지 않고 Discoblocks는 Golang 프로젝트이며 다양한 클라우드 디스크 변형에 대해 여러 유효성 검사기를 지원해야 합니다. 저는 이 영역이 Go의 약점 중 하나라고 생각합니다. 제 말은 새로운 유효성 검사기에는 새로운 바이너리 빌드가 필요하다는 뜻입니다. 미래에 대해 이야기하는 것이 아니라 엄청난 수의 클라우드 드라이버를 지원해야 할 때 이것이 이상적이지 않다는 것을 쉽게 인정할 수 있습니다.

한 가지 옵션은 바이너리를 CGO_ENABLED=1(어쨌든 기본 동작)로 컴파일하고 라이브러리(이 경우 유효성 검사기)를 동적으로 로드하는 것입니다.
  • + 빌트인 솔루션
  • - 컴파일된 언어만 so 파일을 생성할 수 있습니다.
  • - 컨테이너의 모든so 파일을 보장하는 것은 악몽이 될 것입니다. 새 파일은 실행 중인 컨테이너의 디버깅이 필요합니다
  • .
  • - 개발자는 최소한 테스트를 위해 개발을 위해 일부 Linux를 사용해야 합니다
  • .

    쿠버네티스 세계의 대안 솔루션은 Pod의 각각에 대해 사이드카로 작은 유닉스 소켓 기반 HTTP 서비스를 생성하는 것이지만 복잡성 때문에 이 방향으로 이동하고 싶지 않았습니다.

    세 번째 옵션은 유효성 검사기를 WebAssembly, 특히 WASI 모듈로 실행하기 위해 Go 코드를 포주하는 것입니다.

    WASM과 WASI의 차이점은 무엇입니까? 간단히 말해서 WASM은 웹용이며 우리에게 필요한 핵심 기능인 함수 호출을 지원하지 않습니다.
  • + 많은 언어에서 WASI를 생성할 수 있음
  • + WASI 모듈에 추가 종속성이 필요하지 않음
  • + 팬시네요 :)
  • - 여전히 CGO_ENABLED=1가 필요하지만 제한된 수의 라이브러리에만 의존합니다
  • .
  • - 내장 컴파일은 WASI가 아닌 WASM만 지원합니다.
  • WASI 모듈
  • 을 컴파일하려면 TinyGo을 사용해야 합니다.
  • 소수의 숫자 입력 및 출력 유형만 지원됨(해결 방법은 나중에)

  • - WASI의 내장 실행이 누락됨
  • - 통합이 원활하지 않음
  • base64 인코딩이 지원되지 않으므로 내장 파서를 잊어버려야 합니다
  • .
  • 지원되지 않는 기능이 더 있습니다. TinyGo 설명서를 참조하십시오.


  • 내가 가장 좋아하는 코드 타임:

    import (
        "fmt"
        "os"
    
        "github.com/valyala/fastjson"
    )
    
    func main() {}
    
    //export IsStorageClassValid
    func IsStorageClassValid() {
        json := []byte(os.Getenv("STORAGE_CLASS_JSON"))
    
        if fastjson.Exists(json, "volumeBindingMode") && fastjson.GetString(json, "volumeBindingMode") != "WaitForFirstConsumer" {
            fmt.Fprint(os.Stderr, "only volumeBindingMode WaitForFirstConsumer is supported")
            fmt.Fprint(os.Stdout, false)
            return
        }
    
        fmt.Fprint(os.Stdout, true)
    }
    


  • Fastjson은 WASI에서 잘 작동하며 빠릅니다 ;)
  • 엠티func main() {}가 필요합니다

  • 함수
  • 를 내보내려면 //export가 필수입니다.
  • 환경 변수를 읽는 대신 함수에 유형 문제를 피하기 위한 입력 매개변수가 없습니다
  • .
  • 위의 한 줄과 같은 이유로 반환 값이 없으며 대신 표준 출력에 쓰거나 오류가 발생합니다
  • .

    모듈을 컴파일해봅시다.

    go mod init
    docker run -v $(PWD):/go/src/ebs.csi.aws.com -w /go/src/ebs.csi.aws.com tinygo/tinygo:0.23.0 bash -c "go mod tidy && tinygo build -o main.wasm -target wasi --no-debug main.go"
    


    이야기의 다른 측면을 구현할 시간입니다. Go용 WebAssembly 런타임을 찾았습니다. Wasmer-goWasmer을 기반으로 하는 Go용 완전하고 성숙한 WebAssembly 런타임입니다.

    // DriversDir driver location, configure with -ldflags -X github.com/ondat/discoblocks/pkg/drivers.DriversDir=/yourpath
    var DriversDir = "/drivers"
    
    func init() {
        files, err := os.ReadDir(filepath.Clean(DriversDir))
        if err != nil {
            log.Fatal(fmt.Errorf("unable to load drivers: %w", err))
        }
    
        for _, file := range files {
            if !file.IsDir() {
                continue
            }
    
            driverPath := fmt.Sprintf("%s/%s/main.wasm", DriversDir, file.Name())
    
            if _, err := os.Stat(driverPath); err != nil {
                log.Printf("unable to found main.wasm for %s: %s", file.Name(), err.Error())
                continue
            }
    
            wasmBytes, err := os.ReadFile(filepath.Clean(driverPath))
            if err != nil {
                log.Fatal(fmt.Errorf("unable to load driver content for %s: %w", driverPath, err))
            }
    
            engine := wasmer.NewEngine()
            store := wasmer.NewStore(engine)
            module, err := wasmer.NewModule(store, wasmBytes)
            if err != nil {
                log.Fatal(fmt.Errorf("unable to compile module %s: %w", driverPath, err))
            }
    
            drivers[file.Name()] = &Driver{
                store:  store,
                module: module,
            }
        }
    }
    
    var drivers = map[string]*Driver{}
    
    // GetDriver returns given service
    func GetDriver(name string) *Driver {
        return drivers[name]
    }
    
    // Driver is the bridge to WASI modules
    type Driver struct {
        store  *wasmer.Store
        module *wasmer.Module
    }
    
    // IsStorageClassValid validates StorageClass
    func (d *Driver) IsStorageClassValid(sc *storagev1.StorageClass) (bool, error) {
        rawSc, err := json.Marshal(sc)
        if err != nil {
            return false, fmt.Errorf("unable to parse StorageClass: %w", err)
        }
    
        wasiEnv, instance, err := d.init(map[string]string{
            "STORAGE_CLASS_JSON": string(rawSc),
        })
        if err != nil {
            return false, fmt.Errorf("unable to init instance: %w", err)
        }
    
        isStorageClassValid, err := instance.Exports.GetRawFunction("IsStorageClassValid")
        if err != nil {
            return false, fmt.Errorf("unable to find IsStorageClassValid: %w", err)
        }
    
        _, err = isStorageClassValid.Native()()
        if err != nil {
            return false, fmt.Errorf("unable to call IsStorageClassValid: %w", err)
        }
    
        errOut := string(wasiEnv.ReadStderr())
        if errOut != "" {
            return false, fmt.Errorf("function error IsStorageClassValid: %s", errOut)
        }
    
        resp, err := strconv.ParseBool(string(wasiEnv.ReadStdout()))
        if err != nil {
            return false, fmt.Errorf("unable to parse output: %w", err)
        }
    
        return resp, nil
    }
    
    func (d *Driver) init(envs map[string]string) (*wasmer.WasiEnvironment, *wasmer.Instance, error) {
        builder := wasmer.NewWasiStateBuilder("wasi-program").
            CaptureStdout().CaptureStderr()
    
        for k, v := range envs {
            builder = builder.Environment(k, v)
        }
    
        wasiEnv, err := builder.Finalize()
        if err != nil {
            return nil, nil, fmt.Errorf("unable to build module: %w", err)
        }
    
        importObject, err := wasiEnv.GenerateImportObject(d.store, d.module)
        if err != nil {
            return nil, nil, fmt.Errorf("unable to generate imports: %w", err)
        }
    
        instance, err := wasmer.NewInstance(d.module, importObject)
        if err != nil {
            return nil, nil, fmt.Errorf("unable to create instance: %w", err)
        }
    
        start, err := instance.Exports.GetWasiStartFunction()
        if err != nil {
            return nil, nil, fmt.Errorf("unable to get start: %w", err)
        }
    
        _, err = start()
        if err != nil {
            return nil, nil, fmt.Errorf("unable to start instance: %w", err)
        }
    
        return wasiEnv, instance, nil
    }
    


    발신자 측.

    driver := drivers.GetDriver(storageClass.Provisioner)
    if driver == nil {
        return errors.New("driver not found")
    }
    
    valid, err := driver.IsStorageClassValid(&storageClass)
    if err != nil {
        return fmt.Errorf("failed to call driver: %w", err)
    } else if !valid {
        return fmt.Errorf("invalid StorageClass: %w", err)
    }
    


    한 가지 더 있습니다. 모든 것을 컨테이너 이미지로 배송하세요.

    FROM tinygo/tinygo:0.23.0 as drivers
    
    COPY ebs.csi.aws.com/ /go/src/ebs.csi.aws.com
    
    RUN cd /go/src/ebs.csi.aws.com ; go mod tidy && tinygo build -o main.wasm -target wasi --no-debug main.go
    
    ...
    
    FROM redhat/ubi8-micro:8.6
    
    COPY --from=drivers /go/src /drivers
    COPY --from=builder /go/pkg/mod/github.com/wasmerio/[email protected]/wasmer/packaged/lib/linux-amd64/libwasmer.so /lib64
    


    참고로 바이너리가 단일 바이너리로 정적으로 컴파일되지 않기 때문에 scratch 또는 distroless 이미지를 기본으로 사용할 수 없습니다.

    그게 다에요 여러분!!!

    좋은 웹페이지 즐겨찾기