Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/alcounit/selenosis into main
Browse files Browse the repository at this point in the history
  • Loading branch information
alcounit committed Feb 23, 2021
2 parents 68b6666 + e4e5d7b commit c25cf5d
Show file tree
Hide file tree
Showing 13 changed files with 646 additions and 279 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Release
on:
create:
tags:
- v*

jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
build-args: ${{ steps.get_version.outputs.GIT_TAG }}
tags: |
alcounit/selenosis:latest
alcounit/selenosis:${{ steps.get_version.outputs.GIT_TAG }}
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
FROM golang:alpine AS builder

ARG BUILD_VERSION

RUN apk add --quiet --no-cache build-base git

WORKDIR /src
Expand All @@ -13,7 +15,7 @@ RUN go mod download
ADD . .

RUN cd cmd/selenosis && \
go install -ldflags="-linkmode external -extldflags '-static' -s -w"
go install -ldflags="-X main.buildVersion=$BUILD_VERSION -linkmode external -extldflags '-static' -s -w"


FROM scratch
Expand Down
112 changes: 87 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
[![Docker Pulls](https://img.shields.io/docker/pulls/alcounit/selenosis)](https://hub.docker.com/r/alcounit/selenosis/tags?page=1&ordering=last_updated)
# selenosis
Scalable, stateless selenium hub for Kubernetes cluster.

## Overview
### Available flags
```
[user@host]# ./selenosis --help
[user@host]$ ./selenosis --help
Scallable, stateless selenium grid for Kubernetes cluster
Usage:
Expand All @@ -13,15 +14,17 @@ Usage:
Flags:
--port string port for selenosis (default ":4444")
--proxy-port string proxy continer port (default "4445")
--browsers-config string browsers config (default "config/browsers.yaml")
--browsers-config string browsers config (default "./config/browsers.yaml")
--browser-limit int active sessions max limit (default 10)
--namespace string kubernetes namespace (default "default")
--service-name string kubernetes service name for browsers (default "selenosis")
--namespace string kubernetes namespace (default "selenosis")
--service-name string kubernetes service name for browsers (default "seleniferous")
--browser-wait-timeout duration time in seconds that a browser will be ready (default 30s)
--session-wait-timeout duration time in seconds that a session will be ready (default 1m0s)
--session-iddle-timeout duration time in seconds that a session will iddle (default 5m0s)
--session-idle-timeout duration time in seconds that a session will idle (default 5m0s)
--session-retry-count int session retry count (default 3)
--graceful-shutdown-timeout duration time in seconds gracefull shutdown timeout (default 5m0s)
--graceful-shutdown-timeout duration time in seconds gracefull shutdown timeout (default 30s)
--image-pull-secret-name string secret name to private registry
--proxy-image string in case you use private registry replace with image from private registry (default "alcounit/seleniferous:latest")
-h, --help help for selenosis
```
Expand Down Expand Up @@ -51,10 +54,10 @@ Basic configuration be like (all fields in this example are mandatory):
"path": "/",
"versions": {
"85.0": {
"image": "selenoid/vnc:chrome:85.0"
"image": "selenoid/vnc:chrome_85.0"
},
"86.0": {
"image": "selenoid/vnc:chrome:86.0"
"image": "selenoid/vnc:chrome_86.0"
}
}
},
Expand Down Expand Up @@ -92,9 +95,9 @@ chrome:
path: "/"
versions:
'85.0':
image: selenoid/vnc:chrome:85.0
image: selenoid/vnc:chrome_85.0
'86.0':
image: selenoid/vnc:chrome:86.0
image: selenoid/vnc:chrome_86.0
firefox:
defaultVersion: "82.0"
path: "/wd/hub"
Expand All @@ -112,8 +115,6 @@ opera:
'71.0':
image: selenoid/vnc:opera_71.0
```
Browser name and browser version are taken from Selenium desired capabilities.<br/>
Each browser can have default <b>spec/annotations/labels</b>, they will merged to all browsers listed in the <b>versions</b> section.
Expand Down Expand Up @@ -177,10 +178,10 @@ Each browser can have default <b>spec/annotations/labels</b>, they will merged t
},
"versions": {
"85.0": {
"image": "selenoid/vnc:chrome:85.0"
"image": "selenoid/vnc:chrome_85.0"
},
"86.0": {
"image": "selenoid/vnc:chrome:86.0"
"image": "selenoid/vnc:chrome_86.0"
}
}
}
Expand Down Expand Up @@ -225,9 +226,9 @@ chrome:
value: 'true'
versions:
'85.0':
image: selenoid/vnc:chrome:85.0
image: selenoid/vnc:chrome_85.0
'86.0':
image: selenoid/vnc:chrome:86.0
image: selenoid/vnc:chrome_86.0
```
You can override default browser <b>spec/annotation/labels</b> by providing individual <b>spec/annotation/labels</b> to browser version
``` json
Expand Down Expand Up @@ -289,7 +290,7 @@ You can override default browser <b>spec/annotation/labels</b> by providing indi
},
"versions": {
"85.0": {
"image": "selenoid/vnc:chrome:85.0",
"image": "selenoid/vnc:chrome_85.0",
"spec": {
"resources": {
"requests": {
Expand All @@ -304,7 +305,7 @@ You can override default browser <b>spec/annotation/labels</b> by providing indi
}
},
"86.0": {
"image": "selenoid/vnc:chrome:86.0",
"image": "selenoid/vnc:chrome_86.0",
"spec": {
"hostAliases": [
{
Expand Down Expand Up @@ -364,7 +365,7 @@ chrome:
value: 'true'
versions:
'85.0':
image: selenoid/vnc:chrome:85.0
image: selenoid/vnc:chrome_85.0
spec:
resources:
requests:
Expand All @@ -374,7 +375,7 @@ chrome:
memory: 1500Gi
cpu: '1'
'86.0':
image: selenoid/vnc:chrome:86.0
image: selenoid/vnc:chrome_86.0
spec:
hostAliases:
- ip: 127.0.0.1
Expand All @@ -394,8 +395,6 @@ Files and steps required for selenosis deployment available in [selenosis-deploy
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setBrowserName("chrome");
capabilities.setVersion("85.0");
capabilities.setCapability("enableVNC", true);
capabilities.setCapability("enableVideo", false);

RemoteWebDriver driver = new RemoteWebDriver(
URI.create("http://<loadBalancerIP|nodeIP>:<port>/wd/hub").toURL(),
Expand All @@ -408,15 +407,66 @@ from selenium import webdriver
capabilities = {
"browserName": "chrome",
"version": "85.0",
"enableVNC": True,
"enableVideo": False
}

driver = webdriver.Remote(
command_executor="http://<loadBalancerIP|nodeIP>:<port>/wd/hub",
desired_capabilities=capabilities)
```

Note: you can omit browser version in your desired capabilities, make sure you set defaultVersion property in the config file.


## Browser pods are deleted right after start
Depends on you cluster version in some cases you can face with issue when some browser pods are deleted right after their start and selenosis log will contains lines like this:
```log
time="2020-12-21T10:28:20Z" level=error msg="session failed: Post \"http://selenoid-vnc-chrome-87-0-af3177a0-5052-45be-b4e4-9462146e4633.seleniferous:4445/wd/hub/session\": dial tcp: lookup selenoid-vnc-chrome-87-0-af3177a0-5052-45be-b4e4-9462146e4633.seleniferous on 10.96.0.10:53: no such host" request="POST /wd/hub/session" request_id=fa150040-86c1-4224-9e5c-21416b1d9f5c time_elapsed=5.73s
```
To fix this issue do the following:
```bash
kubectl edit cm coredns -n kube-system
```
add following record to your coredns config
```config
selenosis.svc.cluster.local:53 {
errors
kubernetes cluster.local {
namespaces selenosis
}
}
```
this option will turn off dns caching for selenosis namespace, resulting config update should be as following:
```yaml
apiVersion: v1
data:
Corefile: |
selenosis.svc.cluster.local:53 {
errors
kubernetes cluster.local {
namespaces selenosis
}
}
.:53 {
errors
health
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
import custom/*.override
}
import custom/*.server
kind: ConfigMap
...
```

## Features
### Scalability
By default selenosis starts with 2 replica sets. To change it, edit selenosis deployment file: <b>03-selenosis.yaml</b>
Expand All @@ -432,6 +482,12 @@ spec:
selector:
...
```
<br/>
by using kubectl

```bash
kubectl scale deployment selenosis -n selenosis --replicas=3
```

### Stateless
Selenosis doesn't store any session info. All connections to the browsers are automatically assigned via headless service.
Expand All @@ -440,4 +496,10 @@ Selenosis doesn't store any session info. All connections to the browsers are au
Selenosis supports hot config reload, to do so update you configMap
```bash
kubectl edit configmap -n selenosis selenosis-config -o yaml
```
```

### UI for debug
Selenosis itself doesn't have ui. If you need such functionality you can use [selenoid-ui](https://github.com/aerokube/selenoid-ui) with special [adapter container](https://github.com/alcounit/adaptee).
Deployment steps and minifests you can find in [selenosis-deploy](https://github.com/alcounit/selenosis-deploy) repository.

Currently this project is under development and can be unstable, in case of any bugs or ideas please report
47 changes: 28 additions & 19 deletions cmd/selenosis/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"golang.org/x/net/websocket"
)

var buildVersion = "HEAD"

//Command ...
func command() *cobra.Command {

Expand All @@ -30,11 +32,13 @@ func command() *cobra.Command {
proxyPort string
namespace string
service string
imagePullSecretName string
proxyImage string
sessionRetryCount int
limit int
browserWaitTimeout time.Duration
sessionWaitTimeout time.Duration
sessionIddleTimeout time.Duration
sessionIdleTimeout time.Duration
shutdownTimeout time.Duration
)

Expand All @@ -44,7 +48,7 @@ func command() *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {

logger := logrus.New()
logger.Info("starting selenosis")
logger.Infof("starting selenosis %s", buildVersion)

browsers, err := config.NewBrowsersConfig(cfgFile)
if err != nil {
Expand All @@ -58,11 +62,13 @@ func command() *cobra.Command {
logger.Info("config watcher started")

client, err := platform.NewClient(platform.ClientConfig{
Namespace: namespace,
Service: service,
ReadinessTimeout: browserWaitTimeout,
IddleTimeout: sessionIddleTimeout,
ServicePort: proxyPort,
Namespace: namespace,
Service: service,
ReadinessTimeout: browserWaitTimeout,
IdleTimeout: sessionIdleTimeout,
ServicePort: proxyPort,
ImagePullSecretName: imagePullSecretName,
ProxyImage: proxyImage,
})

if err != nil {
Expand All @@ -74,19 +80,20 @@ func command() *cobra.Command {
hostname, _ := os.Hostname()

app := selenosis.New(logger, client, browsers, selenosis.Configuration{
SelenosisHost: hostname,
ServiceName: service,
SidecarPort: proxyPort,
SessionLimit: limit,
SessionRetryCount: sessionRetryCount,
BrowserWaitTimeout: browserWaitTimeout,
SessionIddleTimeout: sessionIddleTimeout,
SelenosisHost: hostname,
ServiceName: service,
SidecarPort: proxyPort,
SessionLimit: limit,
SessionRetryCount: sessionRetryCount,
BrowserWaitTimeout: browserWaitTimeout,
SessionIdleTimeout: sessionIdleTimeout,
BuildVersion: buildVersion,
})

router := mux.NewRouter()
router.HandleFunc("/wd/hub/session", app.HandleSession).Methods(http.MethodPost)
router.HandleFunc("/wd/hub/session", app.CheckLimit(app.HandleSession)).Methods(http.MethodPost)
router.PathPrefix("/wd/hub/session/{sessionId}").HandlerFunc(app.HandleProxy)
router.HandleFunc("/wd/hub/status", app.HadleHubStatus).Methods(http.MethodGet)
router.HandleFunc("/wd/hub/status", app.HandleHubStatus).Methods(http.MethodGet)
router.PathPrefix("/vnc/{sessionId}").Handler(websocket.Handler(app.HandleVNC()))
router.PathPrefix("/logs/{sessionId}").Handler(websocket.Handler(app.HandleLogs()))
router.PathPrefix("/devtools/{sessionId}").HandlerFunc(app.HandleReverseProxy)
Expand Down Expand Up @@ -130,13 +137,15 @@ func command() *cobra.Command {
cmd.Flags().StringVar(&proxyPort, "proxy-port", "4445", "proxy continer port")
cmd.Flags().StringVar(&cfgFile, "browsers-config", "./config/browsers.yaml", "browsers config")
cmd.Flags().IntVar(&limit, "browser-limit", 10, "active sessions max limit")
cmd.Flags().StringVar(&namespace, "namespace", "default", "kubernetes namespace")
cmd.Flags().StringVar(&service, "service-name", "selenosis", "kubernetes service name for browsers")
cmd.Flags().StringVar(&namespace, "namespace", "selenosis", "kubernetes namespace")
cmd.Flags().StringVar(&service, "service-name", "seleniferous", "kubernetes service name for browsers")
cmd.Flags().DurationVar(&browserWaitTimeout, "browser-wait-timeout", 30*time.Second, "time in seconds that a browser will be ready")
cmd.Flags().DurationVar(&sessionWaitTimeout, "session-wait-timeout", 60*time.Second, "time in seconds that a session will be ready")
cmd.Flags().DurationVar(&sessionIddleTimeout, "session-iddle-timeout", 5*time.Minute, "time in seconds that a session will iddle")
cmd.Flags().DurationVar(&sessionIdleTimeout, "session-idle-timeout", 5*time.Minute, "time in seconds that a session will idle")
cmd.Flags().IntVar(&sessionRetryCount, "session-retry-count", 3, "session retry count")
cmd.Flags().DurationVar(&shutdownTimeout, "graceful-shutdown-timeout", 30*time.Second, "time in seconds gracefull shutdown timeout")
cmd.Flags().StringVar(&imagePullSecretName, "image-pull-secret-name", "", "secret name to private registry")
cmd.Flags().StringVar(&proxyImage, "proxy-image", "alcounit/seleniferous:latest", "in case you use private registry replace with image from private registry")
cmd.Flags().SortFlags = false

return cmd
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ require (
k8s.io/api v0.19.3
k8s.io/apimachinery v0.19.3
k8s.io/client-go v0.19.3
k8s.io/kubernetes v0.19.3
k8s.io/utils v0.0.0-20201027101359-01387209bb0d
)
Loading

0 comments on commit c25cf5d

Please sign in to comment.