chore: prepares for stage1
This commit is contained in:
parent
44c1b399d5
commit
6ffd94725a
36
Cargo.toml
Normal file
36
Cargo.toml
Normal file
@ -0,0 +1,36 @@
|
||||
[package]
|
||||
name = "microservice-poc"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[[bin]]
|
||||
name = "auth"
|
||||
path = "src/auth-service/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "client"
|
||||
path = "src/client/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "health-check"
|
||||
path = "src/health-check-service/main.rs"
|
||||
|
||||
[dependencies]
|
||||
tonic = "0.9" # used by all
|
||||
prost = "0.11" # used by all
|
||||
tokio = { version = "1.27", features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
"time",
|
||||
] } # used by all
|
||||
uuid = { version = "1.2", features = [
|
||||
"v4",
|
||||
] } # used by auth and health-check services
|
||||
pbkdf2 = { version = "0.12", features = ["simple"] } # used by auth service
|
||||
rand_core = { version = "0.6", features = ["std"] } # used by auth service
|
||||
clap = { version = "4.2", features = ["derive"] } # used by client
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.9" # used by all
|
94
README.md
Normal file
94
README.md
Normal file
@ -0,0 +1,94 @@
|
||||
# Auth Microservice
|
||||
|
||||
## IMPORTANT NOTE
|
||||
|
||||
___Please read the project description thoroughly BEFORE getting started, especially the FAQs section.___
|
||||
|
||||
___Re-visit the project description multiple times DURING your design and development process, to ensure you're meeting the project requirements.___
|
||||
|
||||
## Problem Statement
|
||||
We will build a microservice app consisting of 2 services, an authentication service and a health check service. We will also build a client that can communicate with the auth service.
|
||||
|
||||

|
||||
|
||||
The auth service will have three primary features:
|
||||
1. Sign in
|
||||
2. Sign up
|
||||
3. Sign out
|
||||
|
||||
## Objective
|
||||
In this project, we aim to learn and practice the following:
|
||||
* Designing, building, and deploying microservices
|
||||
* Using gRPC to communicate between microservices
|
||||
* Monitoring the health of microservices
|
||||
* Setting up continuous integration & continuous deployment
|
||||
* Using session based authentication
|
||||
* Writing testable code
|
||||
* Organizing code using modules
|
||||
* Navigating and contributing to an existing code base
|
||||
|
||||
## Terminologies
|
||||
|
||||
__Session based authentication__
|
||||
|
||||
[Session based auth](https://www.geeksforgeeks.org/session-vs-token-based-authentication/) works by giving the client a session token which can be used in subsequent requests to authenticate the user.
|
||||
|
||||
__Microservices__
|
||||
|
||||
[Microservices](https://microservices.io/) is an architectural style that structures an application as a collection of services that are independently deployable, loosely coupled, organized around business capabilities, and owned by a small team.
|
||||
|
||||
__CI/CD__
|
||||
|
||||
[CI/CD](https://www.redhat.com/en/topics/devops/what-is-ci-cd) (Continuous Integration/Continuous Delivery or Continuous Deployment) is a set of practices and techniques that help software development teams deliver high-quality software faster and more reliably. Continuous Integration refers to the process of frequently merging code changes from multiple developers into a central repository and running automated tests to detect any integration issues early on. Continuous Delivery/Deployment takes this a step further, automating the entire software release process, from building and testing to deploying the application to production. These practices help teams deliver software more frequently and with higher quality, reducing time-to-market and increasing customer satisfaction.
|
||||
|
||||
## Recommendations
|
||||
Here's a list of recommended action items to do during and after the development, to help you more effectively build the project and learn from the project.
|
||||
|
||||
During Development:
|
||||
* __For this project create your own GitHub repo and copy over the code from Stage 1, Step 1. Having your own repro is important for CI/CD to work.__
|
||||
* Check the project description/requirements to make sure you are building what is asked of you.
|
||||
* Utilize the included unit tests to help debug your implementation.
|
||||
* If you get stuck, ask for help in the Discord server or look at the next step for the solution to the current step.
|
||||
* Refactor as you implement. Keep your code clean and compartmentalized. Doing so makes debugging exponentially easier, as your implementation grows.
|
||||
* Make sure your code compiles and all tests are passing (if applicable) before moving on to the next step.
|
||||
|
||||
After Development:
|
||||
* Make sure your CI/CD pipeline is working by pushing up code changes and checking if those changes are deployed to production.
|
||||
* Post your completed project on GitHub. You're a Rust developer now!
|
||||
* Showcase your project to your friends and family (at the very least, to others in the Let's Get Rusty community)!
|
||||
* After completing the project feel free to modify the program by changing the architecture, adding features, etc. This will help you make the project your own and better internalize the lessons you've learned.
|
||||
|
||||
## FAQs
|
||||
|
||||
__Will there be a template to build the project on top of?__
|
||||
|
||||
Yes. Each step has a partially built Rust project for you to finish. Stages and steps build on top of each other until you have a completed project.
|
||||
|
||||
__Should my implementation look exactly like the solution?__
|
||||
|
||||
Your code may differ from the solution, as long as your code compiles, tests are passing, and the program works as intended you are in good shape. Also after completing the project feel free to modify the program by changing the architecture, adding features, etc.
|
||||
|
||||
__What if I get stuck and have questions?__
|
||||
|
||||
If you haven't already, join our Discord server and the exclusive Bootcamp channels as instructed on the Home page of the Bootcamp. Fire away your questions and find project partners over there!
|
||||
|
||||
__NOTE:__ `If you don't know how to implement a TODO item, look at the corresponding test to see what is expected.`
|
||||
|
||||
## Stages Overview
|
||||
The project is split into multiple stages. Please keep in mind, some implementation choices are made to minimize the scope of the project, so we can focus on the learning and implementing Rust related concepts. Here's an overview of the stages:
|
||||
|
||||
### Stage 1
|
||||
|
||||
__Implementing the services & client__
|
||||
|
||||
In this stage we will implement the auth and health check services, set-up communication between them, and create a stand-alone client that can call the auth service.
|
||||
|
||||
### Stage 2
|
||||
|
||||
__Docker & CI/CD__
|
||||
|
||||
In this stage we will "Dockerize" our app, add continuous integration via GitHub Actions, and also implement continuous deployment to DigitalOcean.
|
||||
|
||||
## Get Started!
|
||||
|
||||
Get started by navigating to Stage 1 and reading the README!
|
187
Stage1.md
Normal file
187
Stage1.md
Normal file
@ -0,0 +1,187 @@
|
||||
# Stage 1
|
||||
|
||||
__Overview__
|
||||
|
||||
For this stage, we are going to implement a microservice app consisting of 2 services, an authentication service and a health check service that monitors the auth service. We will also build a client that can communicate with the auth service.
|
||||
|
||||
The services will communicate via [gRPC](https://grpc.io/) & [Protocal Buffers (A.K.A Protobufs)](https://protobuf.dev/). gRPC is a Remote Procedure Call (RPC) framework that allows us to call functions implemented on a remote service the same way we would call functions implemented locally. Protobufs are a way to serialize structured data. Similar to XML or JSON except a lot smaller, faster, and more powerful. We will use Protobufs to define our gRPC services. Once our Protobufs are defined we can automatically generate Rust code that will implement the services.
|
||||
|
||||
## Third Party Libraries
|
||||
|
||||
Rust has a minimal runtime, which means we will need to use several third-party libraries to implement our project.
|
||||
|
||||
### tokio
|
||||
|
||||
[tokio](https://tokio.rs/) is an asynchronous runtime for the Rust programming language. It provides the building blocks needed for writing network applications. We discussed Tokio in the advanced section of the Bootcamp. Re-visit that section if you need a refresher.
|
||||
|
||||
### tonic, prost, and tonic-build
|
||||
|
||||
[tonic](https://crates.io/crates/tonic) is a Rust implementation of gRPC. It is composed of three main components: the generic gRPC implementation, the high performance HTTP/2 implementation and the codegen powered by [prost](https://crates.io/crates/prost).
|
||||
|
||||
[tonic-build](https://crates.io/crates/tonic-build) is a development dependency that we use inside our build script (see `build.rs` below) to compile proto files via `prost` and generate service stubs and proto definitions for use with tonic.
|
||||
|
||||
### pbkdf2 & rand_core
|
||||
|
||||
[pbkdf2](https://crates.io/crates/pbkdf2) and [rand_core](https://crates.io/crates/rand_core) are used to hash passwords.
|
||||
|
||||
### uuid
|
||||
|
||||
[uuid](https://crates.io/crates/uuid) is used to generate unique identifies for each user. It is also used within tests to generate unique strings.
|
||||
|
||||
### clap
|
||||
|
||||
[clap](https://crates.io/crates/clap) is a command-line parser. It will be used to create our stand alone client.
|
||||
|
||||
## Project Structure
|
||||
|
||||
This microservice project will be done inside a [mono-repo](https://www.atlassian.com/git/tutorials/monorepos) which will make development easier.
|
||||
|
||||
```text
|
||||
/[root folder]
|
||||
|__ proto
|
||||
|__ authentication.proto
|
||||
|__ src
|
||||
|__ /auth-service
|
||||
|__ main.rs
|
||||
|__ /client
|
||||
|__ main.rs
|
||||
|__ /health-check-service
|
||||
|__ main.rs
|
||||
|__ build.rs
|
||||
|__ Cargo.toml
|
||||
```
|
||||
|
||||
**proto/authentication.proto**
|
||||
|
||||
This is where we define our authentication service using Protocal Buffers.
|
||||
|
||||
**build.rs**
|
||||
|
||||
This file is a [build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) that we use to compile our Protocal Buffers into Rust code. Cargo will run this build script before compiling our source code.
|
||||
|
||||
**auth-service/main.rs**
|
||||
|
||||
This is the entry point of the authentication service.
|
||||
|
||||
**health-check-service/main.rs**
|
||||
|
||||
This is the entry point of the health check service.
|
||||
|
||||
**client/main.rs**
|
||||
|
||||
This is the entry point of the client.
|
||||
|
||||
**Cargo.toml**
|
||||
|
||||
Thus file defined our 3 binaries crates, `auth`, `client`, and `health-check`. Additionally, all the dependencies required for this project are included as well.
|
||||
|
||||
## Steps
|
||||
|
||||
### Step 1
|
||||
|
||||
__Project setup__
|
||||
|
||||
To start this project follow these steps:
|
||||
|
||||
1. Create a new Rust project by running `cargo new microservice-project`. This is going to be the mono-repo where the microservices and client are stored.
|
||||
2. Create a Github repository and add it as the remote repository for the Rust project you created in the previous step.
|
||||
3. Replace the contents of your Rust project with the files/folders in Step 1.
|
||||
4. Install protoc: https://grpc.io/docs/protoc-installation/
|
||||
|
||||
Your project is now setup. Review all the files and make sure you understand what each one does. Look at the `Third Party Libraries` and `Project Structure` sections above for guidance.
|
||||
|
||||
To make sure everything is working, run each binary:
|
||||
```bash
|
||||
cargo run --bin auth
|
||||
```
|
||||
```bash
|
||||
cargo run --bin health-check
|
||||
```
|
||||
```bash
|
||||
cargo run --bin client
|
||||
```
|
||||
|
||||
### Step 2
|
||||
|
||||
__Implementing the auth service__
|
||||
|
||||
Now that the basic structure of our app is laid out, let's implement the authentication service.
|
||||
|
||||
The auth service is broken up into the following files
|
||||
- `auth-service/users.rs` - Contains logic for creating, storing, retrieving, and deleting users.
|
||||
- `auth-service/sessions.rs` - Contains logic for creating, storing, retrieving, and deleting sessions.
|
||||
- `auth-service/auth.rs` - This is where we will implement the auth service interface as defined in `authentication.proto`.
|
||||
- `auth-service/main.rs` - This is where we will create an instance of the auth service and start the gRPC server.
|
||||
|
||||
Copy these files to your repository.
|
||||
|
||||
Then complete all the TODO items in the following order:
|
||||
1. TODO items in `auth-service/users.rs`
|
||||
2. TODO items in `auth-service/sessions.rs`
|
||||
3. TODO items in `auth-service/auth.rs`
|
||||
4. TODO items in `auth-service/main.rs`
|
||||
|
||||
__NOTE 1:__ Make sure all the tests pass by running `cargo test`.
|
||||
|
||||
__NOTE 2:__ Read through all the code to make sure you understand what's going on.
|
||||
|
||||
__NOTE 3:__ For simplicity this project stores all data on the heap. If the server is restarted all the user/session info will be wiped.
|
||||
|
||||
### Step 3
|
||||
|
||||
__Implementing the health check service__
|
||||
|
||||
The health-check service will continuously make gPRC requests to the auth service and print out the responses.
|
||||
|
||||
Copy over `health-check/main.rs` to your repository.
|
||||
|
||||
To implement the health-check service complete all the TODO items in `health-check/main.rs`.
|
||||
|
||||
__NOTE 1:__ Make sure all the tests pass by running `cargo test`.
|
||||
|
||||
__NOTE 2:__ Read through all the code to make sure you understand what's going on.
|
||||
|
||||
__NOTE 3:__ For simplicity the health check service simply logs responses to standard out. Ideally a health check service would implement more robust logging and alerting.
|
||||
|
||||
### Step 4
|
||||
|
||||
__Implementing the client__
|
||||
|
||||
Now that we've implemented our 2 services it's time to create a CLI client that will allow us to send custom requests to the auth service.
|
||||
|
||||
To implement the client complete all the TODO items in `client/main.rs`.
|
||||
|
||||
__NOTE 1:__ Make sure all the tests pass by running `cargo test`.
|
||||
|
||||
__NOTE 2:__ Read through all the code to make sure you understand what's going on.
|
||||
|
||||
## Running
|
||||
|
||||
After completing steps 1-4 you should have a fully functioning microservice application!
|
||||
|
||||
Let's test it out!
|
||||
|
||||
__NOTE:__ We are going to use [cargo watch](https://github.com/watchexec/cargo-watch) to automatically restart ours services when source files change, so make sure you have it installed.
|
||||
|
||||
First build the project by running `cargo build`.
|
||||
|
||||
Then execute the following commands in different terminal windows:
|
||||
|
||||
```bash
|
||||
cargo watch -c -q -w src/auth-service -x "run -q --bin auth"
|
||||
```
|
||||
```bash
|
||||
cargo watch -c -q -w src/health-check-service -x "run -q --bin health-check"
|
||||
```
|
||||
|
||||
__NOTE:__ These commands should be ran in the order above. The auth service needs to be running before launching the health-check service.
|
||||
|
||||
You should see both services printing to standard out.
|
||||
|
||||
---
|
||||
|
||||
Now let's use the client to issue custom requests to the auth service.
|
||||
|
||||
To see the commands available run `./target/debug/client help`
|
||||
|
||||
Then try signing up a new user, signing in, and signing out.
|
386
Stage2.md
Normal file
386
Stage2.md
Normal file
@ -0,0 +1,386 @@
|
||||
# Stage 2
|
||||
|
||||
__Overview__
|
||||
|
||||
You now have a functioning app however it only runs on your local machine. In this stage we will go through the process of deploying your app so you can show it off to others! We will also setup continuous integration to prevent new code changes from breaking your app.
|
||||
|
||||
## CI/CD Tools
|
||||
|
||||
There are the tools we'll be using to setup CI/CD.
|
||||
|
||||
### Github Actions
|
||||
|
||||
[GitHib Actions](https://docs.github.com/en/actions) allow you to execute arbitrary workflows by simply adding a `YAML` file to your repository.
|
||||
|
||||
Here is a great overview video: https://youtu.be/eB0nUzAI7M8
|
||||
|
||||
### Docker
|
||||
|
||||
[Docker](https://www.docker.com/) is a platform for building, running, and shipping applications in containers.
|
||||
|
||||
Containerization is a technology that allows developers to package an application with all of its dependencies into a standardized unit, called a container, which can be easily deployed across different environments, including local machines, data centers, and cloud providers. Containers are lightweight, portable, and secure, enabling teams to build and deploy applications faster and more reliably.
|
||||
|
||||
Docker images are the blueprints or templates used to create Docker containers. An image contains all the necessary files, libraries, and dependencies required to run an application. A container, on the other hand, is a running instance of an image. It's a lightweight, isolated environment that runs the application and its dependencies. Multiple containers can be created from the same image, each with its own unique state and running independently.
|
||||
|
||||
Here is a great overview video: https://youtu.be/Gjnup-PuquQ
|
||||
|
||||
### DigitalOcean
|
||||
|
||||
[DigitalOcean](https://www.digitalocean.com/) is a cloud computing platform that provides virtual servers (called "Droplets") and other infrastructure services to developers and businesses. It offers a simple and intuitive user interface, along with flexible pricing plans that allow users to pay only for what they use. DigitalOcean supports a wide range of operating systems and application stacks, making it a popular choice for hosting web applications, databases, and other workloads.
|
||||
|
||||
Here are 2 great overview videos: https://youtu.be/goiq9PZLlEU & https://youtu.be/HODYl1KffDE
|
||||
|
||||
## Steps
|
||||
|
||||
### Step 1
|
||||
|
||||
__Configuring Docker__
|
||||
|
||||
Follow these steps to setup Docker:
|
||||
|
||||
1. Create an account on [Docker Hub](https://hub.docker.com/). This is where we will push our images.
|
||||
2. Install Docker Desktop: https://docs.docker.com/get-docker/
|
||||
3. Launch Docker Desktop
|
||||
4. Copy over `.dockerignore`, `Dockerfile-auth`, `Dockerfile-health`, and `docker-compose.yaml` to your repository.
|
||||
5. Inside `docker-compose.yaml` replace "letsgetrusty" with your Docker Hub username.
|
||||
|
||||
__File overview__
|
||||
|
||||
`.dockerignore`
|
||||
|
||||
Similar to `.gitignore` this tells Docker which files/directories it should ignore.
|
||||
|
||||
---
|
||||
|
||||
`Dockerfile-auth` & `Dockerfile-health`
|
||||
|
||||
These are the Docker files for our two services.
|
||||
|
||||
A Dockerfile is a script that contains instructions to build a Docker image. It specifies the base image to use, adds application code, and sets up configuration options. By running a Dockerfile, developers can automate the creation of Docker images, making it easier to deploy and scale applications.
|
||||
|
||||
Let's take a look at the contents of `Dockerfile-auth`:
|
||||
|
||||
```docker
|
||||
FROM rust:1.68.2-alpine3.17 AS chef
|
||||
```
|
||||
|
||||
We start with the official Rust image which has all the dependencies we need to build a Rust project.
|
||||
|
||||
```docker
|
||||
RUN apk add --no-cache musl-dev & cargo install cargo-chef
|
||||
```
|
||||
|
||||
Then we install `musl-dev` and [cargo-chef](https://crates.io/crates/cargo-chef). [musl](https://musl.libc.org/) is an implementation of the C standard library built on top of the Linux system call API. [We need it for `cargo-chef` to work](https://github.com/LukeMathWalker/cargo-chef/blob/5f791e86e87db5bf3add5be5a91e0d06b03c42b4/docker/Dockerfile#L6). `cargo-chef` allows us to cache dependencies in our Rust project and speed up your Docker builds.
|
||||
|
||||
```docker
|
||||
FROM chef AS planner
|
||||
COPY . .
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
FROM chef AS builder
|
||||
COPY --from=planner /microservice-project/recipe.json recipe.json
|
||||
# Build dependencies - this is the caching Docker layer!
|
||||
RUN cargo chef cook --release --recipe-path recipe.json
|
||||
# Build application
|
||||
RUN apk add --no-cache protoc
|
||||
COPY . .
|
||||
RUN cargo build --release --bin auth
|
||||
```
|
||||
|
||||
Then we run `cargo-chef`, install `protoc`, and build the service in release mode.
|
||||
|
||||
```docker
|
||||
FROM debian:buster-slim AS runtime
|
||||
WORKDIR /microservice-project
|
||||
COPY --from=builder /microservice-project/target/release/auth /usr/local/bin
|
||||
ENTRYPOINT ["/usr/local/bin/auth"]
|
||||
```
|
||||
|
||||
Finally we create a new bare-bones image, copy over the binary we created in the previous step, and execute it! One of the advantages of Rust is that our apps can be compiled down to a single binary.
|
||||
|
||||
---
|
||||
|
||||
`docker-compose.yaml`
|
||||
|
||||
[Docker Compose](https://docs.docker.com/compose/) is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. A service is a high-level concept that refers to a set of containers running the same image. Then, with a single command, you create and start all the services from your configuration.
|
||||
|
||||
In our case we need to define an auth service and a health check service:
|
||||
|
||||
```yaml
|
||||
version: "3.9"
|
||||
services:
|
||||
health-check:
|
||||
image: letsgetrusty/health-check # specify name of image on Docker Hub
|
||||
build: # specify which Docker file to use
|
||||
context: .
|
||||
dockerfile: Dockerfile-health
|
||||
restart: "always" # automatically restart container when server crashes
|
||||
depends_on: # ensure that `health-check` starts after `auth` is running
|
||||
auth:
|
||||
condition: service_started
|
||||
auth:
|
||||
image: letsgetrusty/auth # specify name of image on Docker Hub
|
||||
build: # specify which Docker file to use
|
||||
context: .
|
||||
dockerfile: Dockerfile-auth
|
||||
restart: "always" # automatically restart container when server crashes
|
||||
ports:
|
||||
- "50051:50051" # expose port 50051 so that applications outside the container can connect to it
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
After completing the steps above you should be able to start your application via Docker Compose.
|
||||
|
||||
Run `docker-compose up` in the root of your project directory.
|
||||
|
||||
You should see console log output from both services in your terminal.
|
||||
|
||||
Press `CTRL-C` in your terminal window to stop the services.
|
||||
|
||||
Congratulations, you've Dockerized your app and are now ready to deploy it!
|
||||
|
||||
### Step 2
|
||||
|
||||
__Setting up continuous integration__
|
||||
|
||||
Before we talk about continuous deployment let's setup continuous integration.
|
||||
|
||||
Specifically, we will add a GitHub Actions workflow that builds and tests your code before it is merged into master.
|
||||
|
||||
Copy over the `.github/workflow/prod.yml` file to your repository.
|
||||
|
||||
This file defines the workflow. Let's go through it:
|
||||
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
```
|
||||
|
||||
First we define when we want our workflow to run. In this case we want to run the workflow whenever code gets pushed to master or when a pull request targeting the master branch is created.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Install protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
```
|
||||
|
||||
Then we define each step in our `build` job. First we install protoc and checkout the source code.
|
||||
|
||||
```yaml
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
```
|
||||
|
||||
Then we use the cache action to cache the `/.cargo` and `target/` directories. This will decrease compile times between runs.
|
||||
|
||||
```yaml
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
|
||||
- name: Build and test code
|
||||
run: |
|
||||
cargo build --verbose
|
||||
cargo test --verbose
|
||||
```
|
||||
|
||||
Finally we build and test the app.
|
||||
|
||||
---
|
||||
|
||||

|
||||
|
||||
After adding `.github/workflow/prod.yml` to your repository and pushing the changes up to master, you should see your workflow execute.
|
||||
|
||||
### Step 3 (Optional)
|
||||
|
||||
__Setting up continuous deployment__
|
||||
|
||||
***
|
||||
THIS STEP IS OPTIONAL BECAUSE IT REQUIRES ABOUT $5 USD TO COMPLETE. YOU WILL HAVE TO PAY DIGITAL OCEAN TO SPIN UP A DROPLET.
|
||||
***
|
||||
|
||||
To setup continuous deployment follow these steps:
|
||||
|
||||
1. Create a DigitalOcean account: https://www.digitalocean.com/
|
||||
|
||||
2. Create a Digital Ocean droplet. The config I used is listed below but feel free to use your own (*Make sure to use `Password` as the authentication method!*).
|
||||
|
||||
- __Region:__ New York
|
||||
- __Data center:__ NYC 1
|
||||
- __Image:__ Ubuntu 22.10 x64
|
||||
- __Size:__ Shared CPU / Basic / Regular SSD
|
||||
- __Authentication Method:__ Password
|
||||
- __Hostname:__ rust-microservice-project
|
||||
---
|
||||
__NOTE:__ Make sure to save the Droplet's password somewhere safe. We will use it in following steps.
|
||||
|
||||
3. Configure droplet
|
||||
|
||||
Now you should have a machine in the cloud (droplet) which you can deploy to. Next we'll configure that droplet by following these steps:
|
||||
|
||||
- SSH into your droplet: https://docs.digitalocean.com/products/droplets/how-to/connect-with-ssh/
|
||||
- Once connected to your droplet install docker and docker compose:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install docker.io
|
||||
sudo apt-get install docker-compose
|
||||
```
|
||||
|
||||
4. Update GitHub workflow
|
||||
|
||||
Next we will update our GitHub workflow to deploy the app anytime changes are pushed to master.
|
||||
|
||||
Copy over `.github/workflow/prod.yml` to your repository. Notice that now the workflow only runs when changes are pushed to master (not when creating PRs).
|
||||
|
||||
Let's review the rest of the changes:
|
||||
|
||||
```yaml
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker images
|
||||
uses: docker/bake-action@v2.3.0
|
||||
with:
|
||||
push: true
|
||||
set: | # cache Docker layers between runs
|
||||
*.cache-from=type=gha
|
||||
*.cache-to=type=gha,mode=max
|
||||
```
|
||||
|
||||
A few more steps were added to the `build` job. We now build and push the Docker images for our auth and health-check services to Docker Hub. This will allow the deploy step to pull the new images from Docker Hub.
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
```
|
||||
|
||||
A new `deploy` job has also been added. The first couple of steps checkout the source code and log into Docker Hub.
|
||||
|
||||
```yaml
|
||||
- name: Install sshpass
|
||||
run: sudo apt-get install sshpass
|
||||
|
||||
- name: Copy docker-compose.yml to droplet
|
||||
run: sshpass -v -p ${{ secrets.DROPLET_PASSWORD }} scp -o StrictHostKeyChecking=no docker-compose.yaml root@${{ vars.DROPLET_IP }}:~
|
||||
```
|
||||
Next we install `sshpass` to help manage ssh sessions. Then use `scp` to transfer a copy of `docker-compose.yaml` to our droplet. This single file contains all the information needed to build and run our Dockerized app.
|
||||
|
||||
```yaml
|
||||
- name: Deploy
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ vars.DROPLET_IP }}
|
||||
username: root
|
||||
password: ${{ secrets.DROPLET_PASSWORD }}
|
||||
script: |
|
||||
cd ~
|
||||
docker-compose down
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
Finally we ssh into our droplet and deploy the docker containers.
|
||||
|
||||
5. Update Github Secrets & Variables
|
||||
|
||||
You may have noticed that the new `.github/workflow/prod.yml` file has some variables in it (ex: `vars.DROPLET_IP` and `secrets.DROPLET_PASSWORD`). These secrets & variables will need to be defined inside your GitHub repo for the workflow to succeed.
|
||||
|
||||
Secrets are encrypted variables that you can create for a repository. Secrets are available to use in GitHub Actions workflows.
|
||||
|
||||
Add the required secrets by following these steps:
|
||||
1. Navigate to your repository on https://github.com/
|
||||
2. Click on the `Settings` tab
|
||||
3. In the left side-panel click `Secrets and variables` underneath the `Security` section and then click `Actions`.
|
||||
4. Add the following secrets
|
||||
- `DOCKER_USERNAME` - Your Docker Hub username
|
||||
- `DOCKER_PASSWORD` - Your Docker Hub password
|
||||
- `DO_TOKEN` - Your Digital Ocean API token. Create a new token by logging into Digital Ocean, clicking on `API` in the left side-panel and then clicking `Generate New Token`.
|
||||
- `DROPLET_PASSWORD` - You droplet password
|
||||
|
||||

|
||||
|
||||
Now that the secrets are defined we will add one regular variable:
|
||||
|
||||
1. Click on the `Variables` tab in GitHub
|
||||
2. Create a new variable called `DROPLET_IP` and set the value to your droplet's IP address.
|
||||
|
||||

|
||||
|
||||
After adding these secrets/variables you should be able to push your updated `.github/workflow/prod.yml` file to the `master` branch and have your project automatically deployed.
|
||||
|
||||
6. Check droplet
|
||||
|
||||
After the GitHub workflow finishes deploying your project, check that your app is running by following these steps:
|
||||
|
||||
1. SSH into your droplet https://docs.digitalocean.com/products/droplets/how-to/connect-with-ssh/
|
||||
2. Run `docker ps` to see which containers are up
|
||||
|
||||
You should see 2 containers running. Example output:
|
||||
|
||||
```bash
|
||||
root@rust-microservice-project:~# docker ps
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
8805358e487d letsgetrusty/health-check "/usr/local/bin/heal…" 4 days ago Up 4 days root_health-check_1
|
||||
a18f0935f7bb letsgetrusty/auth "/usr/local/bin/auth" 4 days ago Up 17 hours 0.0.0.0:50051->50051/tcp, :::50051->50051/tcp root_auth_1
|
||||
root@rust-microservice-project:~#
|
||||
```
|
||||
|
||||
7. Connect to your droplet
|
||||
|
||||
Finally use your local client to connect to the auth service running in your droplet.
|
||||
|
||||
Run the following command in the root of your project folder:
|
||||
```bash
|
||||
AUTH_SERVICE_IP=555.555.555.55 cargo run --bin client
|
||||
```
|
||||
|
||||
Replace `555.555.555.55` with your droplets IP address.
|
||||
|
||||
## Final Note
|
||||
|
||||
Congratulations! You have built fully functioning microservices app in Rust!
|
||||
|
||||
You should be proud of your progress if you've gotten this far.
|
||||
|
||||
Showcase your implementation and struggles you've faced along the way to others in the Let's Get Rusty community.
|
||||
|
||||
More importantly, teaching is the best way to learn. Any questions posted by others in the Discord channels are opportunities for you to answer and truly internalize your knowledge.
|
4
build.rs
Normal file
4
build.rs
Normal file
@ -0,0 +1,4 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_build::compile_protos("proto/authentication.proto")?;
|
||||
Ok(())
|
||||
}
|
63
flake.nix
Normal file
63
flake.nix
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
description = "Example Rust development environment for Zero to Nix";
|
||||
|
||||
# Flake inputs
|
||||
inputs = {
|
||||
# nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/*.tar.gz";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay"; # A helper for Rust + Nix
|
||||
# cargo2nix.url = "github:cargo2nix/cargo2nix/";
|
||||
nixpkgs.follows = "rust-overlay/nixpkgs";
|
||||
};
|
||||
|
||||
# Flake outputs
|
||||
outputs = { self, nixpkgs, rust-overlay}:
|
||||
let
|
||||
# Overlays enable you to customize the Nixpkgs attribute set
|
||||
overlays = [
|
||||
# Makes a `rust-bin` attribute available in Nixpkgs
|
||||
(import rust-overlay)
|
||||
# Provides a `rustToolchain` attribute for Nixpkgs that we can use to
|
||||
# create a Rust environment
|
||||
(self: super: {
|
||||
rustToolchain = super.rust-bin.stable.latest.default;
|
||||
})
|
||||
];
|
||||
|
||||
# Systems supported
|
||||
allSystems = [
|
||||
"x86_64-linux" # 64-bit Intel/AMD Linux
|
||||
"aarch64-linux" # 64-bit ARM Linux
|
||||
"x86_64-darwin" # 64-bit Intel macOS
|
||||
"aarch64-darwin" # 64-bit ARM macOS
|
||||
];
|
||||
|
||||
# rustTarget = nixpkgs.pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override {
|
||||
# extensions = [ "rust-src" "rustup" "rust-analyzer" "rust-std" ];
|
||||
# targets = [ "x86_64-unknown-linux-gnu" ];
|
||||
# });
|
||||
|
||||
# Helper to provide system-specific attributes
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
|
||||
pkgs = import nixpkgs { inherit overlays system; };
|
||||
});
|
||||
in
|
||||
{
|
||||
# Development environment output
|
||||
devShells = forAllSystems ({ pkgs }: {
|
||||
default = pkgs.mkShell {
|
||||
# shellHook = ''
|
||||
# rustup target add wasm32-unknown-unknown
|
||||
# '';
|
||||
# The Nix packages provided in the environment
|
||||
packages = (with pkgs; [
|
||||
# The package provided by our custom overlay. Includes cargo, Clippy, cargo-fmt,
|
||||
# rustdoc, rustfmt, and other tools.
|
||||
protobuf
|
||||
rust-analyzer
|
||||
clippy
|
||||
rustToolchain
|
||||
]) ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [ libiconv ]);
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
BIN
microservices.png
Normal file
BIN
microservices.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 623 KiB |
41
proto/authentication.proto
Normal file
41
proto/authentication.proto
Normal file
@ -0,0 +1,41 @@
|
||||
syntax = "proto3";
|
||||
package authentication;
|
||||
|
||||
service Auth {
|
||||
rpc SignUp (SignUpRequest) returns (SignUpResponse);
|
||||
rpc SignIn (SignInRequest) returns (SignInResponse);
|
||||
rpc SignOut (SignOutRequest) returns (SignOutResponse);
|
||||
}
|
||||
|
||||
message SignUpRequest {
|
||||
string username = 1;
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
message SignUpResponse {
|
||||
StatusCode statusCode = 1;
|
||||
}
|
||||
|
||||
message SignInRequest {
|
||||
string username = 1;
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
message SignInResponse {
|
||||
StatusCode statusCode = 1;
|
||||
string userUuid = 2;
|
||||
string sessionToken = 3;
|
||||
}
|
||||
|
||||
message SignOutRequest {
|
||||
string sessionToken = 1;
|
||||
}
|
||||
|
||||
message SignOutResponse {
|
||||
StatusCode statusCode = 1;
|
||||
}
|
||||
|
||||
enum StatusCode {
|
||||
FAILURE = 0;
|
||||
SUCCESS = 1;
|
||||
}
|
3
src/auth-service/main.rs
Normal file
3
src/auth-service/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("auth service");
|
||||
}
|
3
src/client/main.rs
Normal file
3
src/client/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("client");
|
||||
}
|
3
src/health-check-service/main.rs
Normal file
3
src/health-check-service/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("health check service");
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user