Introduction

Microservices is a software architectural design where a 'single' application is made up of a collection of different services that can be deployed independently. Though complex to initially set up, it is my general opinion that microservices offers benefits that outweigh this initial complexity.

In this article we will describe and go through a very simple approach stepwise to start your Kubernetes project.

Why you should use Microservices

  1. Failure of one service does not affect the other services
  2. Independent service scalability
  3. Right tool for each service
  4. Easier service independent debugging
  5. Easy continuous delivery

Prerequisites

  1. Docker hub account
  2. Java 8+
  3. AWS Free tier account

Step 1: Getting Started

For this project you should have these tools and have your envrionment setup:
Install Java SDK
Install Maven
Install Visual Studio Code IDE
Set up a MYSQL DB Instance in AWS and ensure it is of version 8, use the cheaper t2.micro instance, and enable public access.

Ensure to set up the PATH environment variable and install the following VS code extensions: Spring Boot tools, Java extension pack, Maven for Java, Spring Boot Dashboard, Spring Boot Extension Pack.

Folder structure

Create a folder for your project with the following structure:

├── .github
│   └── workflows
│       ├── depl-ingredients.yaml
│       ├── depl-manifests.yaml
│       └── depl-recipe.yaml
├── infra
│   └── k8s
│       ├── apps
│       │   ├── depl-ingredients.yaml
│       │   └── depl-recipe.yaml
│       └── ingress
│           └── depl-ingress.yaml
├── ingredients
└── recipe

The workflows folder will contain Github actions for deployment.
The infra folder will contain instructions for setting up the different Kubernetes resources.
The ingredients and the recipe will contain the Microservices.

Creating the SpringBoot project

Head over to Spring Initialzr and Generate 2 projects i.e recipe and ingredients with the following options:

  • Language: Java
  • Project: Maven project
  • Spring Boot: 2.3.3
  • Packaging: JAR
  • Java: 8

Extract the genarated zipped files into the ingredients and recipe folders respectively.

Step 2: Setting up Ingredients Microservice

In the ingredients folder add the following files and folders:

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── chef
│   │   │           └── ingredients
│   │   │               ├── controllers
│   │   │               │   └── IngredientsController.java
│   │   │               ├── exceptions
│   │   │               │   ├── CustomException.java
│   │   │               │   └── CustomizedResponseEntityExceptionHandler.java
│   │   │               ├── IngredientsApplication.java
│   │   │               ├── models
│   │   │               │   └── Ingredients.java
│   │   │               ├── repos
│   │   │               │   └── IngredientsRepos.java
│   │   │               ├── requests
│   │   │               │   └── IngredientsRequest.java
│   │   │               ├── responses
│   │   │               │   ├── GeneralResponse.java
│   │   │               │   └── StatusResponse.java
│   │   │               └── services
│   │   │                   └── IngredientsService.java
│   │   └── resources
│   │       └── application.properties
│   └── test
│       └── java
│           └── com
│               └── chef
│                   └── ingredients
│                       └── IngredientsApplicationTests.java

Step 3: Add Required Dependencies

The best way to add dependencies is to add them in the pom.xml file. Therefore, in the pom.xml add the following dependencies:

// To enable Rest
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

// For Swagger Documentation
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.2.32</version>
</dependency>

// To Enable MySQL Data
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

//To connect to MySQL
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

Step 4: Connecting to the Database

In the application.properties files, add the below configuration properties.
We shall store some of these variables in the environment later on in step 14.


spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}

# Enable Auto Updating of new tables and table columns
spring.jpa.hibernate.ddl-auto=update

# Specify the jdbc driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#Direct hibernate to work with mysql 8
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL8Dialect

#Configure Table and Column Naming strategies
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl


Step 5: Creating the Required Models

Spring Boot uses Entities to map a class to an Entity in the Database, in this case a table. Spring Boot will automatically create these tables and map these attributes to columns in the tables based on the @Entity, @Id, @Column annotations.

The GenerationType.IDENTITY is used to instruct the primary key to be table specific rather than project wide.

Create the Ingredients class in the Ingredients.java file as shown below using setters and getters to acheive encapsulation.


package com.chef.ingredients.models;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity(name = "ingredient")
public class Ingredients {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false, unique = true)
    private Integer id;

    @Column(name = "email", nullable = false, unique = true)
    private String email;

    @Column(name = "name", nullable = false, unique = true)
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Step 6: Setting up the Repositories

Repositories are Spring Boot's way of providing interfaces between the business logic and the database entities to enable simple and complex CRUD operations to the database.

The interfaces should extend the JPA Repository to expose a set of relevant methods that will in the background generate relevant mysql queries as shown below.

Remember to pass in the model class name and the data type of the primary key field


package com.chef.ingredients.repos;

import com.chef.ingredients.models.Ingredients;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface IngredientsRepos extends JpaRepository<Ingredients, Integer> {

}

Step 7: Configuring Responses

To achieve uniformity in our responses, create 2 files responses/GeneralResponse.java and responses/StatusResponse.java as shown below.

The GeneralResponse class will have a dynamic data attribute to help it accomodate any type of data expected in a get method response.

The StatusResponse class will be focuses on responses that do not need to return data.


package com.chef.ingredients.responses;

import java.time.LocalDateTime;

public class StatusResponse {

    private LocalDateTime time;
    private String message;
    private Integer code;

    public StatusResponse() {
        super();
    }

    public StatusResponse(LocalDateTime time, String message, Integer code) {
        super();
        this.time = time;
        this.message = message;
        this.code = code;
    }

    public LocalDateTime getTime() {
        return time;
    }

    public void setTime(LocalDateTime time) {
        this.time = time;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    @Override
    public String toString() {
        return "StatusResponse [time=" + time + ", message=" + message + ", code=" + code + "]";
    }
}


package com.chef.ingredients.responses;

import java.time.LocalDateTime;

public class GeneralResponse<T> {

    private LocalDateTime time;
    private String message;
    private Integer code;
    private T data;

    public GeneralResponse() {
        super();
    }

    public GeneralResponse(LocalDateTime time, String message, Integer code, T data) {
        super();
        this.time = time;
        this.message = message;
        this.code = code;
        this.data = data;
    }

    public LocalDateTime getTime() {
        return time;
    }

    public void setTime(LocalDateTime time) {
        this.time = time;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "GeneralResponse [time=" + time + ", message=" + message + ", code=" + code + ", data=" + data + "]";
    }

}

Step 8: Configuring Requests

It is not advisable to use Model classes in making http requests since they may have fields such as dates etc.

To avoid this define a request class in the requests/IngredientsRequest.java as shown below:


package com.chef.ingredients.requests;

public class IngredientsRequest {

    private String email;

    private String name;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}


Step 9: Customizing Exceptions

Customize Exceptions by using the classes below in the exceptions folder.

This should be done to give Exceptions message flexibility.

package com.chef.ingredients.exceptions;

@SuppressWarnings("serial")
public class CustomException extends RuntimeException {

    private Integer code;

    private String message;

    public CustomException(Integer code, String message) {
        super();
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

}

Use this class to register CustomException as a valid project exception.

package com.chef.ingredients.exceptions;

import java.time.LocalDateTime;

import com.chef.ingredients.responses.StatusResponse;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
@RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Exception.class)
    public final ResponseEntity<StatusResponse> handleExceptions(Exception ex, WebRequest request) {
        StatusResponse statusResponse = new StatusResponse(LocalDateTime.now().plusHours(3), ex.getMessage(), 500);
        return new ResponseEntity<>(statusResponse, new HttpHeaders(), HttpStatus.OK);
    }

    @ExceptionHandler(CustomException.class)
    public final ResponseEntity<StatusResponse> customExceptionHandling(CustomException ex,
            WebRequest request) {
        StatusResponse statusResponse = new StatusResponse(LocalDateTime.now().plusHours(3), ex.getMessage(),
                ex.getCode());
        return new ResponseEntity<>(statusResponse, new HttpHeaders(), HttpStatus.OK);
    }

}

Step 10: Creating the Business Logic

With respect to MVC, separate the business logic into the services folder.
Define the IngredientsService.java as shown below.

The @Autowired annotation is used to inject the Ingredients Repository as a dependency to the service class.
Repository Methods can then be accessed using the injected dependency.


package com.chef.ingredients.services;

import java.util.List;
import java.util.Optional;

import com.chef.ingredients.exceptions.CustomException;
import com.chef.ingredients.models.Ingredients;
import com.chef.ingredients.repos.IngredientsRepos;
import com.chef.ingredients.requests.IngredientsRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class IngredientsService {

    @Autowired
    private IngredientsRepos ingredientsRepos;

    public void createIngredient(IngredientsRequest ingredientsRequest) throws Exception {

        Ingredients ing = new Ingredients();
        ing.setEmail(ingredientsRequest.getEmail());
        ing.setName(ingredientsRequest.getName());

        ingredientsRepos.save(ing);

    }

    public void updateIngredient(Ingredients ingredients) throws Exception {

        Optional<Ingredients> findById = ingredientsRepos.findById(ingredients.getId());

        if (!findById.isPresent()) {
            throw new CustomException(401, "Not Found");
        }

        Ingredients ingredientsNew = findById.get();
        ingredientsNew.setEmail(ingredients.getEmail());
        ingredientsNew.setName(ingredients.getName());

        ingredientsRepos.save(ingredientsNew);

    }

    public void deleteIngredient(Integer id) throws Exception {

        Optional<Ingredients> findById = ingredientsRepos.findById(id);

        if (!findById.isPresent()) {
            throw new CustomException(401, "Not Found");
        }

        ingredientsRepos.deleteById(id);

    }

    public Ingredients getIngredient(Integer id) throws Exception {

        Optional<Ingredients> findById = ingredientsRepos.findById(id);

        if (!findById.isPresent()) {
            throw new CustomException(401, "Not Found");
        }

        return findById.get();
    }

    public List<Ingredients> getAllIngredient() throws Exception {

        List<Ingredients> findAll = ingredientsRepos.findAll();

        if (findAll.isEmpty()) {
            throw new CustomException(401, "Not Found");
        }

        return findAll;
    }

}


Step 11: Setting up the Controllers

The Rest controller defined by the @RestController is used to define the Rest endpoints that will be used to access the Rest API.

The @Crossorigin annotation is used to define cors.

Define it as shown below:


package com.chef.ingredients.controllers;

import java.time.LocalDateTime;
import java.util.List;

import com.chef.ingredients.models.Ingredients;
import com.chef.ingredients.requests.IngredientsRequest;
import com.chef.ingredients.responses.GeneralResponse;
import com.chef.ingredients.responses.StatusResponse;
import com.chef.ingredients.services.IngredientsService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin(origins = "*", allowedHeaders = "*")
@RequestMapping("/ingredients/v1")
public class IngredientsController {

    @Autowired
    private IngredientsService ingredientsService;

    @PostMapping(path = "/create")
    public StatusResponse createIngredient(@RequestBody IngredientsRequest ingredientsRequest) throws Exception {

        ingredientsService.createIngredient(ingredientsRequest);

        StatusResponse stat = new StatusResponse(LocalDateTime.now().plusHours(3), "Created", 200);
        return stat;
    }

    @PutMapping(path = "/update")
    public StatusResponse updateIngredient(@RequestBody Ingredients ingredients) throws Exception {

        ingredientsService.updateIngredient(ingredients);

        StatusResponse stat = new StatusResponse(LocalDateTime.now().plusHours(3), "updated", 200);
        return stat;
    }

    @DeleteMapping(path = "/delete")
    public StatusResponse deleteIngredient(Integer id) throws Exception {

        ingredientsService.deleteIngredient(id);

        StatusResponse stat = new StatusResponse(LocalDateTime.now().plusHours(3), "deleted", 200);
        return stat;
    }

    @GetMapping(path = "/read")
    public GeneralResponse<Ingredients> readIngredient(Integer id) throws Exception {

        Ingredients ingredient = ingredientsService.getIngredient(id);

        GeneralResponse<Ingredients> gen = new GeneralResponse<Ingredients>(LocalDateTime.now().plusHours(3), "Found",
                200, ingredient);
        return gen;
    }

    @GetMapping(path = "/readAll")
    public GeneralResponse<List<Ingredients>> readAllIngredient() throws Exception {

        List<Ingredients> ingredient = ingredientsService.getAllIngredient();

        GeneralResponse<List<Ingredients>> gen = new GeneralResponse<List<Ingredients>>(
                LocalDateTime.now().plusHours(3), "Found", 200, ingredient);
        return gen;
    }

}

Step 12: Documenting the API

Use springdoc-openapi to autogenerate Swagger Open-api documentation. This package will autogenerate REST Documentation and any Data Models within the projects based on the relevant classes.

Ensure you add these dependencies in the pom.xml file to have the documentation API:


<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.2.32</version>
</dependency>

Step 13: Running your Project

Set up your environment variables i.e the DB_URL, DB_USER, DB_PASSWORD locally.

While in the ingredients directory, use the following command to run your project:

mvn spring-boot:run

The project should run successfully.

Step 14: Creating the Dockerfile

Set up your dockerfile as shown below with the correct environment variables. Kurbernetes requires the dockerfile to create a docker image.

FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

ENV DB_URL=jdbc:mysql://<host>:3306/ingredients
ENV DB_USER=<name>
ENV DB_PASSWORD=<passs>


Step 15: Creating your Kubernetes Cluster

Head over to AWS EKS and create a new cluster.

Once the cluster is ready, add a preferred number of t3.micro nodes as a node group for the cluster.

Step 16: Setting up Github

Besides Repositories, we shall use github actions to automatically deploy our project to an existing cluster.

Create a Github Repository.
And for this work ensure you have an active dockerhub account.

Navigate to the settings tab and create the following secrets:

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • CLUSTER_NAME
  • REGION_CODE
  • DOCKER_PASSWORD
  • DOCKER_USERNAME

Step 17: Creating the Kubernetes Deployment and Service Definition

The Kuberentes architecture is made up of

  1. Pod - An instance of the service
  2. Deployment - collection of Pods
  3. Service - Define a deployment/pod as a service to enable communication.

Define the deployment and its corresponding service in the infra/k8s/apps/depl-ingredients.yaml file as shown below.


apiVersion: apps/v1

kind: Deployment

metadata:
  name: ing
  labels:
    app: ing

spec:
  replicas: 2

  selector:
    matchLabels:
      app: ing

  template:
    metadata:
      labels:
        app: ing

    spec:
      containers:
        - name: ing
          image: anyungu/ing
          ports:
            - containerPort: 8080
      serviceAccountName: ing

---
apiVersion: v1
kind: Service
metadata:
  name: ing-srv

spec:
  type: NodePort
  selector:
    app: ing
  ports:
    - port: 80
      name: ing
      targetPort: 8080


Step 18: Defining the Kubernetes ingress

Use the Nginx ingress controller to direct traffic:
link

Select the cloud appropriate command from this link. This will be applied in our cluster later on.

Define rules for the ingress in the infra/k8s/ingress/depl-ingresss.yaml as shown below:


apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: k8s-learn-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  # tls:
  #   - hosts:
  #       - k8s24.lanthanion.com
  #     secretName: k8s-learn-tls
  rules:
    #  - host: k8s24.lanthanion.com
    - http:
        paths:
          - path: /ingredients/?(.*)
            backend:
              serviceName: ing
              servicePort: 80


Step 19: Setting up Deployment Workflow using Github Actions

Set up github actions to ensure continous delivery.

Github Actions for Deploying Kubernetes Manifests

In the .github/workflows/depl-manifests file define the following workflow.
This will be used to apply all the Kuberenetes yaml configs onto our cluster.

This workflow will always trigger whenever there is a change in the infra/k8s/** folder and a push/merge into master branch will be made.


name: depl-manifests

on:
  push:
    branches:
      - masters
    paths:
      - infra/k8s/**

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-west-1

      - name: Set up kubernetes context
        run: aws eks --region ${{ secrets.REGION_CODE }} update-kubeconfig --name ${{ secrets.CLUSTER_NAME }}

      - name: set up the Ingress controller
        run: kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.35.0/deploy/static/provider/aws/deploy.yaml

      - name: Delete webhook validation
        run: kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission

      - name: Run manifests
        run: kubectl apply -f infra/k8s/ --recursive --kubeconfig /home/runner/.kube/config


Github Actions for Redeploying the Ingredients Service

In the .github/workflows/depl-manifests file define the following workflow.
This will be used to apply the Kuberenetes configuration specific to the ingredients service.
This will always get triggered whenever there is a change in the ingredients service.

The below command will restart the ingredients deployment with the new docker image resulting from changes in the ingredients service.

kubectl rollout restart deployment/ing


name: depl-ingredients

on:
  push:
    branches:
      - master
    paths:
      - ingredients/**

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up JDK 8
        uses: actions/setup-java@v1
        with:
          java-version: 8

      - name: Maven Package
        run: |
          cd ingredients
          mvn -B clean package -DskipTests

      - name: Build and push Docker images
        uses: docker/build-push-action@v1
        with:
          path: ingredients
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
          repository: anyungu/ing
          tags: latest
        env:
          DB_URL: ${{secrets.DB_URL}}
          DB_USER: ${{secrets.DB_USER}}
          DB_PASSWORD: ${{secrets.DB_PASSWORD}}

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-west-1

      - name: Set up kubernetes context
        run: aws eks --region ${{ secrets.REGION_CODE }} update-kubeconfig --name ${{ secrets.CLUSTER_NAME }}

      - name: Deploy
        run: |
          kubectl rollout restart deployment/ing

Step 20: Deployment and Testing

Git Push

A first and second push to the master branch (with changes triggering the workflows) will guarantee creation of the relevant resources to your cluster.

Once a push/merge to master is completed, Workflow Activity can be viewed in real time at the Actions tab in your github project repository.

AWS CLI and kubectl

kubectl is the tool used to connect to a Kubernetes cluster and context.

Download and install AWS CLI

Configure the AWS CLI with this command

 aws configure

Download and install Kubectl and set up the EKS cluster context using this command:

aws eks --region region-code update-kubeconfig --name cluster_name

You can now run apply, debug and delete commands to your cluster such as


kubectl get svc

kubectl get deployments

kubectl get pods

kubectl logs <pod-id>

kubectl delete pods --all --all-namespace

kubectl apply -f <path to yaml file>

kubectl get ingress

kubectl delete deployments --name <deployment name>

kubectl delete -f <path to yaml file>

More commands can be found here

Test with Postman

From the AWS EC2 Service sidebar you will see the Load Balancer and from the it you will find the Network load balancer created by ingress.

Use the provided DNS Name as the Gateway URL to your cluster.

You can send http requests to your notifications service.

Conclusion and Remarks

This tutorial walked you through the process of setting up and deployment of a minimalistic Microservices using Kubernetes, Spring Boot and Github Actions. You can find the full code on Github

You can use Microservices to quickly help you breakdown a monolithic application and have different services run independently with required performance.

This Example is focused on AWS, however this can also be set up with other cloud providers.

Besides, in a production environment, have and put in place more security considerations

You've successfully subscribed to Decoded For Devs
Welcome back! You've successfully signed in.
Great! You've successfully signed up.
Your link has expired
Success! Your account is fully activated, you now have access to all content.