Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

s2i: Add img support #33

Merged
merged 2 commits into from
Sep 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,3 @@ Please see the [Design Document](./docs/design.md) to know the architecture of C

- Thank [kubeflow/kubeflow](https://github.com/kubeflow/kubeflow) for the awesome operators which supports TensorFlow/PyTorch and many other ML frameworks on Kubernetes.
- Thank [gopherdata/gophernotes](https://github.com/gopherdata/gophernotes) for the reference implementation of Jupyter Kernel in Golang.
- Thank [openshift/source-to-image](https://github.com/openshift/source-to-image) for the tool to convert source code to Docker image directly.
27 changes: 25 additions & 2 deletions cmd/kubeflow-kernel/command/run.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package command

import (
"fmt"
"log"

kubeflowbackend "github.com/caicloud/ciao/pkg/backend/kubeflow"
"github.com/caicloud/ciao/pkg/config"
simpleinterpreter "github.com/caicloud/ciao/pkg/interpreter/simple"
"github.com/caicloud/ciao/pkg/kernel"
"github.com/caicloud/ciao/pkg/manager"
"github.com/caicloud/ciao/pkg/s2i"
imgs2i "github.com/caicloud/ciao/pkg/s2i/img"
simples2i "github.com/caicloud/ciao/pkg/s2i/simple"
"github.com/caicloud/ciao/version"
"github.com/spf13/cobra"
Expand All @@ -29,7 +33,7 @@ func init() {
}

func run(cmd *cobra.Command, args []string) {
kubeConfig := viper.GetString("kubeconfig")
kubeConfig := viper.GetString(config.KubeConfig)
if kubeConfig == "" {
log.Fatalln("Failed to start the kernel: Kubeconfig missed")
}
Expand All @@ -45,7 +49,15 @@ func run(cmd *cobra.Command, args []string) {
log.Fatalf("Error building kubeflow backend: %s\n", err.Error())
}

s2iClient := simples2i.New()
s2iConfig := viper.GetStringMapString(config.S2I)
if s2iConfig == nil {
log.Fatalf("Error creating s2i client: Failed to find the config\n")
}

s2iClient, err := createS2IClient(s2iConfig)
if err != nil {
log.Fatalf("Error creating s2i client: %s\n", err.Error())
}

interpreter := simpleinterpreter.New()

Expand All @@ -56,3 +68,14 @@ func run(cmd *cobra.Command, args []string) {
log.Println("Running Kubeflow kernel for Jupyter...")
ciao.RunKernel()
}

func createS2IClient(s2iConfig map[string]string) (s2i.Interface, error) {
switch s2iConfig[config.S2IProvider] {
case config.S2IProviderS2I:
return simples2i.New(), nil
case config.S2IProviderImg:
return imgs2i.New(), nil
default:
return nil, fmt.Errorf("Failed to find the provider %s", s2iConfig[config.S2IProvider])
}
}
13 changes: 12 additions & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,25 @@ Then we need to create a configuration file `$HOME/.ciao/config.yaml`:

```yaml
kubeconfig: {path to your kubeconfig}
s2i:
provider: {img or s2i}
```

## Install S2I Builder Image
There are two options about tools to convert the source code in Jupyter Notebook to Docker image:

- [img](https://github.com/genuinetools/img) (Recommended), which is a daemon-free tool to build and push Docker images.
- [s2i](https://github.com/openshift/source-to-image), which is a source to image tool.

## Install Image

For better performance, we recommend pulling the builder images from Docker Registry ahead of time. There are two builder images for different ML frameworks:

- `gaocegege/tensorflow-s2i:1.10.1-py3`
- `gaocegege/pytorch-s2i:v0.2`
- `tensorflow/tensorflow:1.10.1-py3`
- `pytorch/pytorch:v0.2`

Or the time of the first run will be extremely long (which depends on your network).

## Run the Kernel

Expand Down
9 changes: 9 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package config

const (
KubeConfig = "kubeconfig"
S2I = "s2i"
S2IProvider = "provider"
S2IProviderS2I = "s2i"
S2IProviderImg = "img"
)
74 changes: 74 additions & 0 deletions pkg/s2i/img/img.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package img

import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"

"github.com/caicloud/ciao/pkg/types"
)

const (
prefix = "kubeflow-kernel-code."
codeFile = "code.py"
dockerFile = "Dockerfile"
imageOwner = "gaocegege"
)

// Client is the type for using img.
type Client struct {
}

// New creates a new Client.
func New() *Client {
return &Client{}
}

// SourceToImage converts the code to the image.
func (c Client) SourceToImage(code string, parameter *types.Parameter) (string, error) {
// This is a hack to let kubernetes do not pull from docker registry.
imageName := fmt.Sprintf("%s:v1", filepath.Join(imageOwner, parameter.GenerateName))

dir, err := ioutil.TempDir(os.TempDir(), prefix)
if err != nil {
return "", err
}

err = ioutil.WriteFile(filepath.Join(dir, codeFile), []byte(code), 0666)
if err != nil {
return "", err
}

if err = c.writeDockerfile(dir, parameter); err != nil {
return "", err
}

cmd := exec.Command("img", "build", "-t", imageName, dir)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we need to pre-install img? what if notebook itself is running in a pod?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then img should be added into the image, with runc and some other deps.

output, err := cmd.Output()
if err != nil {
fmt.Printf("[kubeflow] Failed to build the image: %s", string(output))
return "", err
}

fmt.Printf("[kubeflow] Pushing the image...\n")
cmd = exec.Command("img", "push", imageName)
output, err = cmd.Output()
if err != nil {
fmt.Printf("[kubeflow] Failed to push the image: %s", string(output))
return "", err
}
return imageName, nil
}

func (c Client) writeDockerfile(dir string, parameter *types.Parameter) error {
var template string
switch parameter.Framework {
case types.FrameworkTypeTensorFlow:
template = tensorflowTemplate
case types.FrameworkTypePyTorch:
template = pytorchTemplate
}
return ioutil.WriteFile(filepath.Join(dir, dockerFile), []byte(template), 0666)
}
13 changes: 13 additions & 0 deletions pkg/s2i/img/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package img

const (
tensorflowTemplate = `FROM tensorflow/tensorflow:1.10.1-py3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure it will always work; most of the time, we need a lot libraries other than tensorflow. Do you have any solution in mind? I think this is not specific to ciao, but a general question.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current stage, I prefer all-in-one support.

In the future, I think we should support adding new layers in the image to add dependencies while I have no detailed design for it since it is out of the scope of ciao now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rough approaches:

  1. analyze python source code and install imported packages, or use %package xxx syntax (slow)
  2. wrap add all commonly used packages in a base image (huge image)
  3. others..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created an issue for it. #35

LABEL maintainer="Kubeflow"
COPY ./code.py /
ENTRYPOINT ["python", "/app/code.py"]`

pytorchTemplate = `FROM pytorch/pytorch:v0.2
LABEL maintainer="Kubeflow"
COPY ./code.py /
ENTRYPOINT ["python", "/code.py"]`
)
5 changes: 2 additions & 3 deletions pkg/s2i/simple/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ const (
codeFile = "code.py"
builderImageTF = "gaocegege/tensorflow-s2i:1.10.1-py3"
builderImagePyTorch = "gaocegege/pytorch-s2i:v0.2"
// builderImagePyTorch = "gaocegege/pytorch-s2i:0.4_cuda9_cudnn7"
imageOwner = "caicloud"
imageOwner = "caicloud"
)

// S2IClient is the type for using s2i.
Expand Down Expand Up @@ -47,7 +46,7 @@ func (s S2IClient) SourceToImage(code string, parameter *types.Parameter) (strin
cmd.Dir = dir
output, err := cmd.Output()
if err != nil {
fmt.Printf("[kubeflow] Failed build the image: %s", string(output))
fmt.Printf("[kubeflow] Failed to build the image: %s", string(output))
return "", err
}

Expand Down
2 changes: 1 addition & 1 deletion s2i/pytorch-s2i/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM pytorch/pytorch:0.4_cuda9_cudnn7
FROM pytorch/pytorch:v0.2

LABEL maintainer="Kubeflow"

Expand Down