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

Getting Image-hub functional with Istio #47

Merged
merged 28 commits into from
Mar 1, 2021
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
66 changes: 52 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,67 @@
Image Hub is a sample application written to run on [Consul](https://meshery.layer5.io/docs/service-meshes/adapters/consul) for exploring WebAssembly modules used as Envoy filters. This demo application has been enabled by experimental works of [Nic Jackson](https://twitter.com/sheriffjackson) of HashiCorp, and [Kanishkar J](https://twitter.com/_kanishkarj_), [Lee Calcote](https://twitter.com/lcalcote), and other contributors of Layer5.


### Deployment Instructions (pending [meshery-consul/issues/2](https://github.com/layer5io/meshery-consul/issues/2)):
## Deployment Instructions

Image Hub supports Envoy-based data planes. Deployment instructions for each supported service mesh are below.

### Using Istio (pending [PR #196](https://github.com/layer5io/meshery-istio/pull/196)+release; clone and do make run for now):
1) Use [Meshery](https://github.com/layer5io/meshery) to deploy [istio](https://github.com/layer5io/advanced-istio-service-mesh-workshop/blob/master/lab-1/README.md) and the Image Hub sample application (Management > Istio > Manage Sample Application Lifecycle > Image-Hub ) onto the Istio service mesh.
2) To map `imagehub.meshery.io` to the appropriate IP, run the following command to add the appropriate entry in the `"\etc\hosts"` file:

```
echo $(kubectl get nodes --selector=kubernetes.io/role!=master -o jsonpath={.items[0].status.addresses[?\(@.type==\"InternalIP\"\)].address})' 'imagehub.meshery.io | sudo tee -a /etc/hosts
```
3) To get the environment port, run the following command:
```
echo $(kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.spec.ports[1].nodePort}')
```
4) Access the web UI using:
```
http://imagehub.meshery.io:<environment port>
```


### Using Consul:

1) Deploy the latest Consul:

```bash
helm repo add hashicorp https://helm.releases.hashicorp.com # Adds helm hashicorp repo
helm install consul hashicorp/consul -f config/consul-values.yaml # Setup custom Consul with support for WASM
```
```bash
helm repo add hashicorp https://helm.releases.hashicorp.com # Adds helm hashicorp repo
leecalcote marked this conversation as resolved.
Show resolved Hide resolved
helm install consul hashicorp/consul -f config/consul-values.yaml # Setup custom Consul with support for WASM
```

2) Use [Meshery](https://github.com/layer5io/meshery) to deploy the Image Hub sample application onto the Consul service mesh.

### Use Image Hub
3) Find the port assigned to the `ingess` service:

```
kubectl get svc ingess
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingess NodePort 10.97.34.25 <none> 80:31118/TCP 27m
```

4) Open http://localhost:31118 (where 31118 is your environment's port number).

## Use Image Hub

1. Upon visiting the image-hub homepage, we would need to signup for an account! Go ahead and click the "Sign Up" button in the top right.
1. Enter the login details, and select a plan; Personal gives you 10 pulls per minute, Team gives you 100 pulls per minute, and Enterprise will give you 1000 pulls per minute.
1. After signing up, you should be redirected to the login page, where you can login and start using the sample app.
1. On the main page, open up the network network tab, and reload the page. This will allow you to see the request-response of the app.
1. Go ahead and click the download button a couple of times. You'll notice that there is nothing actually limiting you from crossing the number of pulls according to the plan you chose!
1. Alternatively you could test the above by navigating to http://imagehub.meshery.io:<environment port>/pull, and then looking at the resquest-responses.

## Deploying the Rate Limiter WASM Filter for Envoy

1. Find the port assigned to the `ingess` service:
1. Go back to Management > Istio and under "Apply Service Mesh Configuration" make sure to apply "Automatic Sidecar Injection"
1. Open up a terminal, and run `kubectl get pods` to get a list of running pods. You should be able to see 2 pods, `web` and `api`. Now run
the command `kubectl delete pods <exact web pod name> <exact api pod name>`. This will cause kubernetes to respawn them with the updated configuration.
1. Go back to Management > Istio and under "Apply Service Mesh Configuration", select the `Envoy Filter for Image Hub` option, and wait for a few seconds.

```
kubectl get svc ingess
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingess NodePort 10.97.34.25 <none> 80:31118/TCP 27m
```
## Use Image hub with a WASM filter

1. Open http://localhost:31118 (where 31118 is your environment's port number).
1. Test your ability to "pull" an image (images are not in fact pulled, but an HTTP request is sent to the backend `api`). You should not be able to pull an image.
1. Test your ability to "pull" an image (images are not in fact pulled, but an HTTP request is sent to the backend `api`). You would not be able to pull an image, and the response would say "unauthorized".
1. Sign up a new user and select a subscription plan.
1. Login as that user.
1. Test your ability to "pull" an image. You should be able to pull an image.
Expand Down
4 changes: 3 additions & 1 deletion rate-limit-filter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ edition = "2018"
crate-type = ["cdylib"]

[dependencies]
proxy-wasm = "0.1.0"
wasm-bindgen = "0.2"
proxy-wasm = "^0.1"
serde = { version = "1.0", default-features = false, features = ["derive"] }
bincode = "1.0"
#postgres = "^0.19.0"
base64 = "0.12.1"
serde_json ="1.0"
1 change: 1 addition & 0 deletions rate-limit-filter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# wasm_test
Binary file added rate-limit-filter/pkg/rate_limit_filter_bg.wasm
Binary file not shown.
39 changes: 36 additions & 3 deletions rate-limit-filter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod rate_limiter;

//use postgres::{Client, Error, NoTls};
use proxy_wasm::traits::*;
use proxy_wasm::types::*;
use rate_limiter::RateLimiter;
Expand All @@ -16,14 +17,31 @@ pub fn _start() {
}

#[derive(Debug)]
struct UpstreamCall {}
struct UpstreamCall {
//paths: Vec<String>,
}

impl UpstreamCall {
fn new() -> Self {
return Self {};
return Self {
//paths: retrieve().unwrap(),
};
}
}

/*
fn retrieve() -> Result<Vec<String>, Error> {
let mut client = Client::connect("host=localhost user=postgres dbname=mesherydb", NoTls)?;
let array = client
.query("SELECT PathName FROM Paths", &[])?
.iter()
.map(|x| x.get(0))
.collect();
Ok(array)
}
*/

//to be removed
static ALLOWED_PATHS: [&str; 4] = ["/auth", "/signup", "/upgrade", "/pull"];
leecalcote marked this conversation as resolved.
Show resolved Hide resolved
static CORS_HEADERS: [(&str, &str); 5] = [
("Powered-By", "proxy-wasm"),
Expand Down Expand Up @@ -52,6 +70,12 @@ impl HttpContext for UpstreamCall {
return Action::Continue;
}
}
/*
if let Some(path) = self.get_http_request_header(":path") {
if self.paths.binary_search(&path.to_string()).is_ok() {
return Action::Continue;
}
}*/
if let Some(header) = self.get_http_request_header("Authorization") {
if let Ok(token) = base64::decode(header) {
let obj: Data = serde_json::from_slice(&token).unwrap();
Expand All @@ -61,13 +85,22 @@ impl HttpContext for UpstreamCall {
let mn = (tm.as_secs() / 60) % 60;
let _sc = tm.as_secs() % 60;
let mut rl = RateLimiter::get(obj.username, obj.plan);

let mut headers = CORS_HEADERS.to_vec();
let count: String;

if !rl.update(mn as i32) {
self.send_http_response(429, CORS_HEADERS.to_vec(), Some(b"Limit exceeded.\n"));
count = rl.count.to_string();
headers.append(&mut vec![("x-rate-limit", &count), ("x-app-user", &rl.key)]);
self.send_http_response(429, headers, Some(b"Limit exceeded.\n"));
rl.set();
return Action::Pause;
}
proxy_wasm::hostcalls::log(LogLevel::Debug, format!("Obj {:?}", &rl).as_str()).ok();
count = rl.count.to_string();
rl.set();
headers.append(&mut vec![("x-rate-limit", &count), ("x-app-user", &rl.key)]);
self.send_http_response(200, headers, Some(b"All Good!\n"));
return Action::Continue;
}
}
Expand Down
11 changes: 6 additions & 5 deletions rate-limit-filter/src/rate_limiter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RateLimiter {
rpm: Option<u32>,
min: i32,
count: u32,
key: String,
pub rpm: Option<u32>,
pub min: i32,
pub count: u32,
pub key: String,
}

impl RateLimiter {
Expand Down Expand Up @@ -58,7 +58,8 @@ impl RateLimiter {
proxy_wasm::hostcalls::log(
LogLevel::Debug,
format!("Obj {:?} {:?}", self.count, self.rpm).as_str(),
).ok();
)
.ok();
if let Some(sm) = self.rpm {
if self.count > sm {
return false;
Expand Down