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

Docker cloud #10

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
36fa649
Initial support for testing
omnifroodle May 18, 2016
581ea2d
move unit test to the src/test/java so it is properly picked up by Ma…
epugh May 23, 2016
e8e11e3
scope normally is the last attribute
epugh May 23, 2016
eee6e6b
small fixes from running through the process.
epugh May 23, 2016
74e8861
eclipse friendly ignores
epugh Aug 26, 2016
9db597f
rolled in basics of Docker Cloud support
epugh Aug 26, 2016
0d092f6
move LinkedContainerManager interface to better home
epugh Aug 26, 2016
3bab944
little bits of cleanup
epugh Aug 26, 2016
217d49e
now actually testing out a docker image to see if it is deployable or…
epugh Aug 27, 2016
5f17b9c
upgrade to Dropwizard 1.0.0, deal with wiremock dependency issues
epugh Aug 27, 2016
6ab71f6
Java is hard! one more set of dependencies, now the Dropwizard start…
epugh Aug 27, 2016
53e2248
enough changes going on to call this a 1.1!
epugh Aug 27, 2016
f8ba333
trying to provide better docs
epugh Aug 27, 2016
d577bb0
standardize on .yml instead of the mix we have
epugh Aug 28, 2016
d671e3b
dont just blindly assume a stack has services, cause it may be just s…
epugh Aug 28, 2016
aa0f83d
allow property interpolation and hopefully keep my crednetiasl out ou…
epugh Aug 28, 2016
6f8b47a
for now, grab the default application, still needs reworking
epugh Aug 28, 2016
326e48b
one itsy bitsy ! was needed
epugh Aug 29, 2016
8fe8139
removed hard coded stack definition, following more pattern laid out …
epugh Sep 9, 2016
5a56be3
fixes to running GC w dockercloud, better tests
epugh Sep 10, 2016
2a2b50f
shorten name, and add more debuggin
epugh Sep 10, 2016
73fdcc1
Running and Partly Running both count to be in GC
epugh Sep 11, 2016
eaa604d
making starting up faster by backgrounding delete, use a better test …
epugh Sep 28, 2016
86d3e43
instead of running admin on a seperate port, put it on main port, but…
epugh Sep 29, 2016
2414354
fix up some docs on how to develop
epugh Sep 29, 2016
69ba1ef
Version bump
epugh Sep 29, 2016
00a265e
fix the deleteing of extra stacks
epugh Sep 29, 2016
1002777
mention why nodes are deleted
epugh Oct 1, 2016
08f41b3
need to pick the right front end service to point GC incoming traffic…
epugh Oct 1, 2016
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,7 @@ dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties


# Eclipse
.classpath
.settings
.project
6 changes: 3 additions & 3 deletions Dockerfile.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ RUN yum install -y openssl
RUN mkdir -p /srv/app/config

COPY target/grand-central-1.0-SNAPSHOT.jar /srv/app/
COPY config/configuration.yaml /srv/app/config/
COPY config/pod.yaml /srv/app/config/
COPY config/configuration.yml /srv/app/config/
COPY config/pod.yml /srv/app/config/

RUN echo -n | openssl s_client -connect <KUBERNETES_MASTER_IP>:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > /srv/app/config/k8s.pem
RUN keytool -importkeystore -srckeystore /usr/java/latest/lib/security/cacerts -destkeystore /srv/app/config/grandcentral.jks -srcstorepass changeit -deststorepass changeit
RUN echo "yes" | keytool -import -v -trustcacerts -alias local_k8s -file /srv/app/config/k8s.pem -keystore /srv/app/config/grandcentral.jks -keypass changeit -storepass changeit

CMD cd /srv/app && /usr/bin/java -jar /srv/app/grand-central-1.0-SNAPSHOT.jar server /srv/app/config/configuration.yaml
CMD cd /srv/app && /usr/bin/java -jar /srv/app/grand-central-1.0-SNAPSHOT.jar server /srv/app/config/configuration.yml
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,34 @@ sudo route add -net 10.2.47 172.17.4.99
## Local K8S Certificate
The K8S cluster has a self-signed SSL certificate. It must be added to a keystore as a trusted certificate before requests are permitted.

To retrieve K8S master ip, assuming using GCP, you need to first get your username/password via:

```
gcloud container clusters describe hello-zeppelin --zone us-central1-f
```

Then you can look up the IP of the K8S dashboard via

```
kubectl cluster-info | grep kubernetes-dashboard
```


**OS X**

```
brew install openssl
echo -n | /usr/local/Cellar/openssl/1.0.2e/bin/openssl s_client -connect <kubernetes master ip>:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > config/local.pem
echo -n | /usr/local/Cellar/openssl/1.0.2g/bin/openssl s_client -connect <kubernetes master ip>:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > config/local.pem
keytool -importkeystore -srckeystore $JAVA_HOME/jre/lib/security/cacerts -destkeystore config/grandcentral.jks -srcstorepass changeit -deststorepass changeit
ho "yes" | keytool -import -v -trustcacerts -alias local_k8s -file k8s/local.pem -keystore config/grandcentral.jks -keypass changeit -storepass changeit
echo "yes" | keytool -import -v -trustcacerts -alias local_k8s -file config/local.pem -keystore config/grandcentral.jks -keypass changeit -storepass changeit
```

**Linux**

```
echo -n | openssl s_client -connect 172.17.4.99:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > config/local.pem
keytool -importkeystore -srckeystore $JAVA_HOME/jre/lib/security/cacerts -destkeystore config/grandcentral.jks -srcstorepass changeit -deststorepass changeit
echo "yes" | keytool -import -v -trustcacerts -alias local_k8s -file k8s/local.pem -keystore config/grandcentral.jks -keypass changeit -storepass changeit
echo "yes" | keytool -import -v -trustcacerts -alias local_k8s -file config/local.pem -keystore config/grandcentral.jks -keypass changeit -storepass changeit
```

### Logging in to GCR.io
Expand Down
88 changes: 88 additions & 0 deletions README_dockercloud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Grand Central
*Quepid's automated review deployment tool. Gut-check in the cloud*

Grand Central is a tool for automated deployment and cleanup of developer review environments / containers. Requests are parsed and routed based on their URL structure. If the target container exists the request is proxied along. If not Grand Central will spin up a container and forward the request once it comes online.

## URL Structure
The appropriate container is determined by parsing the first part of the domain name. `*.review.quepid.com` in DNS is directed at the Grand Central service. The application parses the domain name to retrieve the appropriate Git version to deploy. `http://db139cf.review.quepid.com/secure` would route to a container running version `db139cf` if it exists.

## Request Flow
When a request is received by the system the following processing takes place.

1. Validate the git version supplied in the `Host` header. *Does it match a valid short hash signature?*
1. Verify if the version is currently running
* If so, proxy the request
* If not, continue
1. Verify version exists in Container Registry. *We can't deploy a version which doesn't exist.
* If so, continue
* If not, 404
1. Create DockerCloud Stack (?) containing app version, database, and loader
1. Create Service for Stack (routes requests internally within the cluster)
1. Proxy the original request

*Note* that all requests will have some metrics stored to determine activity for a give stack. This is useful when reaping old stacks.

**BUT WAIT!** *What happens when two requests come in for the same version?*

Easy, state is maintained within an Atomically accessed Map. As soon as a version is detected to not be running we instantiate it before releasing the lock. If a version exists, but isn't running we pause the request until the creation process is complete on another thread.

## Stack Creation
Should a container not be running in the DockerCloud cluster when the request is performed the following process starts.

1. Create a Stack with the following components (ERIC: IS THIS AT ALL RIGHT?)
* Rails Application
* MySQL Instance
* Data dump loader (golden review image stored block store)
1. Create a service referencing that stack

## Stack Clean-up
Every hour a janitorial task runs which cleans up stacks / services that have not received a request in the past *x* seconds. This keeps cluster resources available. Since stacks are trivial to create they can be re-instantiated easily.

## Resource Constraints
There is a hard limit to the number of simultaneous stacks running on the cluster. To prevent Denial of Service by our own team the maximum number of review environments is capped at *y*. Should a new stack be requested when the currently running count is already maxed the stack with the oldest most recent request will be removed.

## Future Features
* Persistent hashes - the ability to mark a version as persistent. This prevents the janitor from reaping the pod.
* Admin interface - allow a RESTful interface to manage stacks. List all stacks, create a new one, delete an old one out of the janitorial process etc.
* Security?!


## Curling

curl --user username:apikey "https://cloud.docker.com/api/app/v1/stack/"

curl --user username:apikey "https://cloud.docker.com/api/app/v1/stack/"

## Development/Testing
There are a lot of moving pieces to GrandCentral, you need GC itself, plus the configuration to work with either Kubernetes or DockerCloud. This is how I set up my local environment:

1. Set up your /etc/hosts with a couple of fake DNS entries that map to two released versions of Apache. You can see these tags at https://hub.docker.com/r/eboraas/apache/tags/.

```
127.0.0.1 latest.apache.grandcentral.com
127.0.0.1 stretch.apache.grandcentral.com
```

1. I like to run the core GrandCentral application in Eclipse in debug mode. Fortunately that is very easy. Just setup a _Java Application_ run/debug configuration. In the _Arguments_ tab tell Dropwizard to run in _server_ mode and pass in the configuration file: `server src/test/resources/local-dockercloud.yml`. Then in the _Enrivonment_ tab, add an entry for `DOCKERCLOUD_APIKEY` and `DOCKERCLOUD_USERNAME`.

1. Fire up the application, and you'll see some startup checks that verify access to DockerCloud.

1. Browse to http://latest.apache.grandcentral.com:8080 and in about 10 seconds you should see a default Debian Apache install page load up! Check your DockerCloud dashboard, you'll see the service fired up and running on an internal port. Then, pull up http://stretch.apache.grandcentral.com:8080 and you'll see the new pod started, and the old pod deleted due to the _maximum_stack_count=1_.


## /etc/hosts

Add to make testing your local set up easier this to your `/etc/hosts` file:

```
127.0.0.1 v1.datastart.grandcentral.com
127.0.0.1 v2.datastart.grandcentral.com
```


## Dockerizing GrandCentral


## Implementation

All logic for checking / creation of pods may be performed in a [`javax.servlet.Filter`](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html?is-external=true). Requests may then be passed along to a [`org.eclipse.jetty.proxy.ProxyServlet`](http://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/proxy/ProxyServlet.html) after stack management is complete.
8 changes: 7 additions & 1 deletion config/configuration.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ gcloud:
project: quepid-1051
container_name: quails


dockercloud:
protocol: https
hostname: cloud.docker.com
namespace: datastart
stack_json_path: src/test/resources/docker-cloud.json
username: ${DOCKERCLOUD_USERNAME}
apikey: ${DOCKERCLOUD_APIKEY}
11 changes: 11 additions & 0 deletions config/docker-cloud.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "echostack",
"services": [
{
"name":"echoheaders",
"please_note": "Grand Central replaces __DOCKER_TAG__ in the image with the version",
"image": "gcr.io/google_containers/echoserver:__DOCKER_TAG__",
"ports": ["8080:8080"]
}
]
}
File renamed without changes.
58 changes: 53 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
<name>Grand Central</name>
<groupId>com.o19s</groupId>
<artifactId>grand-central</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.1-SNAPSHOT</version>

<properties>
<dropwizard.version>0.9.1</dropwizard.version>
<jetty.version>9.2.13.v20150730</jetty.version>
<dropwizard.version>1.0.2</dropwizard.version>
<jetty.version>9.3.9.v20160517</jetty.version>
<junit.version>4.12</junit.version>
</properties>

<dependencies>
Expand All @@ -30,6 +31,53 @@
<artifactId>jetty-proxy</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.1.11</version>
<scope>test</scope>
<exclusions>
<exclusion> <!-- brings in older version of Jackson... -->
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion> <!-- brings in older version of Jackson... -->
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
<exclusion> <!-- brings in older version of Jackson... -->
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion> <!-- brings in older version of Jetty... -->
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</exclusion>
<exclusion> <!-- brings in older version of Jetty... -->
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
</exclusion>
<exclusion> <!-- brings in older version of Jetty... -->
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
</exclusion>
<exclusion> <!-- brings in older version of Jetty... -->
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
</exclusion>
<exclusion> <!-- brings in older version of Jetty... -->
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
</exclusion>
</exclusions>

</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -70,7 +118,7 @@
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.o19s.grandcentral.GrandCentralApplication</mainClass>
<mainClass>com.o19s.grandcentral.GrandCentralApplication2</mainClass>
</transformer>
</transformers>
</configuration>
Expand All @@ -79,4 +127,4 @@
</plugin>
</plugins>
</build>
</project>
</project>
18 changes: 10 additions & 8 deletions src/main/java/com/o19s/grandcentral/GrandCentralApplication.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package com.o19s.grandcentral;

import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

import java.util.EnumSet;

import javax.servlet.DispatcherType;

import com.o19s.grandcentral.gcloud.GCloudRegistry;
import com.o19s.grandcentral.healthchecks.ContainerRegistryHealthCheck;
import com.o19s.grandcentral.healthchecks.KubernetesMasterHealthCheck;
import com.o19s.grandcentral.kubernetes.PodManager;
import com.o19s.grandcentral.servlets.PodProxyServlet;
import com.o19s.grandcentral.servlets.PodServletFilter;
import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

import javax.servlet.DispatcherType;
import java.util.EnumSet;

public class GrandCentralApplication extends Application<GrandCentralConfiguration> {
public static void main(String[] args) throws Exception {
Expand Down Expand Up @@ -44,15 +46,15 @@ public void run(GrandCentralConfiguration config, Environment environment) throw
config.getKubernetesConfiguration().getNamespace()));

// Build the PodManager
PodManager podManager = new PodManager(
LinkedContainerManager podManager = new PodManager(
config.getKubernetesConfiguration(),
config.getKeystorePath(),
config.getRefreshIntervalInMs(),
config.getMaximumPodCount(),
config.getPodYamlPath()
);

GCloudRegistry gCloudRegistry = new GCloudRegistry(config.getGCloudConfiguration(), config.getKeystorePath());
ImageRegistry gCloudRegistry = new GCloudRegistry(config.getGCloudConfiguration(), config.getKeystorePath());

// Define the filter and proxy
final PodServletFilter psv = new PodServletFilter(config.getGrandcentralDomain(), podManager, gCloudRegistry);
Expand Down
87 changes: 87 additions & 0 deletions src/main/java/com/o19s/grandcentral/GrandCentralApplication2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.o19s.grandcentral;

import io.dropwizard.Application;
import io.dropwizard.configuration.EnvironmentVariableSubstitutor;
import io.dropwizard.configuration.SubstitutingSourceProvider;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

import java.util.EnumSet;

import javax.servlet.DispatcherType;

import com.o19s.grandcentral.dockercloud.DockercloudRegistry;
import com.o19s.grandcentral.dockercloud.StackManager;
import com.o19s.grandcentral.servlets.PodProxyServlet;
import com.o19s.grandcentral.servlets.PodServletFilter;
//import com.o19s.grandcentral.healthchecks.ContainerRegistryHealthCheck;
//import com.o19s.grandcentral.healthchecks.KubernetesMasterHealthCheck;

public class GrandCentralApplication2 extends Application<GrandCentralConfiguration2> {
public static void main(String[] args) throws Exception {
new GrandCentralApplication2().run(args);
}

@Override
public String getName() {
return "Grand Central";
}

@Override
public void initialize(Bootstrap<GrandCentralConfiguration2> bootstrap) {
// Enable variable substitution with environment variables
bootstrap.setConfigurationSourceProvider(
new SubstitutingSourceProvider(bootstrap.getConfigurationSourceProvider(),
new EnvironmentVariableSubstitutor(false)
)
);

}

@Override
public void run(GrandCentralConfiguration2 config, Environment environment) throws Exception {

// FIXME: Should be a healthcheck that confirms access to DockerCloud.
System.out.println("Username:" + config.getDockercloudConfiguration().getUsername());
// Add health checks
/*
environment.healthChecks().register("container_registry", new ContainerRegistryHealthCheck(
config.getKeystorePath(),
config.getGCloudConfiguration().getRegistryDomain(),
config.getGCloudConfiguration().getProject(),
config.getGCloudConfiguration().getContainerName(),
config.getGCloudConfiguration().getRegistryUsername(),
config.getGCloudConfiguration().getRegistryPassword()));
environment.healthChecks().register("kubernetes_master", new KubernetesMasterHealthCheck(
config.getKubernetesConfiguration().getMasterIp(),
config.getKeystorePath(),
config.getKubernetesConfiguration().getUsername(),
config.getKubernetesConfiguration().getPassword(),
config.getKubernetesConfiguration().getNamespace()));
*/

// Build the StackManager
LinkedContainerManager linkedContainerManager = new StackManager(
config.getDockercloudConfiguration(),
config.getRefreshIntervalInMs(),
config.getMaximumStackCount()
);


ImageRegistry imageRegistry = new DockercloudRegistry(config.getDockercloudConfiguration());


// Define the filter and proxy
final PodServletFilter psv = new PodServletFilter(config.getGrandcentralDomain(), linkedContainerManager, imageRegistry);
final PodProxyServlet pps = new PodProxyServlet(config.getPodPort());

// Disable Jersey in the proxy environment
environment.jersey().disable();

// Setup Servlet filters and proxies
environment.servlets().addFilter("Pod Servlet Filter", psv)
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
environment.servlets().addServlet("Pod Proxy Servlet", pps)
.addMapping("/*");
}
}
Loading