Deploy Highchart Export Server in AWS ECS

 


In the previous article, we saw how to generate the highchart export server docker image. But, now we are going to deploy it in AWS ECS. An AWS service for running containers.

GitHub Actions

Our source code is on Git Hub. And thanks to GitHub actions we can deploy directly to AWS ECS. I will show you that it is very simple.


1. In the top menu you will find the Actions option. Go there to set up the integration with AWS ECS.


2. Github Actions has several workflows. In our case, we will use the AWS ECS one.


3. You will then be presented with a .github/workflows folder where there will be a manifest called aws.yml with the instructions to create a docker image and deploy it to an ECS cluster service following the instructions of a task definition.



4. The integration with your AWS account is super simple, you only need your ACCESS_ID and your SECRET_ID. These will be added in the settings - secrets and variables - actions option, as shown below.


5. At this point, we stopped working on GitHub because we need to create the architecture components in AWS: ELB, ECR, and ECS (Cluster, Service and task definition).


AWS Security Group

1. We need a security group to use in the creation of the following components that allow us to work with the HTTP, HTTPS, and SSH (optional) protocols.



AWS Target Groups

1. We need to create two target groups for both ports: 8080 and 7000.
The target group for port 8080:



Note: This configuration is for port 8080, as can be seen, it is expected to have a /health to verify the health of the service. Make sure that your API that is going to run in the container has this endpoint implemented and responding, otherwise, it will give you a lot of container restart problems.

The target group for port 7000:


Note: If you remember in the previous article we implemented the health check in port 7000. So it is ready to be used in the ELB.


AWS Elastic Load Balancer

1. We must create an ELB for our solution with 2 target groups: One for port 8080 and another for port 7000 (health check). Also, configure for the use of the HTTPs protocol and your certificate.



This part is where you use the 2 target groups created earlier.


AWS Elastic Container Registry

1. We will create a repository for our docker image, but we will sync it with the production docker registry, so if you deploy it to your development environment, the same image goes to production. 

Note: The synchronization between docker registries of different regions is simple, just follow this link.


Copy the link of your docker repository because you will use it later.


AWS Elastic Container Service

1. We will create an ECS Cluster with Fargate.



2. Next it is necessary to create a task definition where we will specify the docker image to use and how many instances we want to create.




After creating it, do not forget to download the JSON of the configuration made.





3.  The file will be put in our GitHub repo for development and production environments.


Note: At this point, you will have realized that you will have to create the same components (Security group, target groups, ELB, ECS, ECR) in your production account. So you will get the task definition for production.


4. Now we have to create the service in the cluster where we will use our task definition.


But, unfortunately, we cannot create a service, associating an ELB with 2 target groups. The console web does not allow it, I am going to show you:


Fill in the following information with our task definition and define a number of 3 replicas.


Fill in the information about networking:



Fill in the information of ELB with the target groups. This is where we will see that we cannot add another target group to the ELB that we had already created and we will have to abort the creation of the service through the web console. Pretty bad.



So you will have to create the service, but without setting the ELB information. Be careful, the first part works for us.


5. Fortunately you can update the service and associate it to the ELB and its two target groups by command line with the Aws CLI.


aws ecs update-service \                                                                                                 
    --cluster HighchartServerCluster \
    --service highchartserver-service \
    --load-balancers "[{\"containerName\": \"hightchartserver-app\", \"containerPort\": 8080, \"targetGroupArn\":
    \"arn:aws:elasticloadbalancing:eu-north-1:NNNNNNNNNNN:targetgroup/HighchartServerALBTargetGroup/ace1b83ad311d35e\"}, {\"containerName\": \"hightchartserver-app\", \"containerPort\": 7000, \"targetGroupArn\": \"arn:aws:elasticloadbalancing:eu-north-1: NNNNNNNNNNN:targetgroup/HighchartServerHealthTargetGroup/4c3c7200d3ee2998\"}]"

6. The cluster then deploys the service according to your task definition.


Note: If you have any questions or problems up to here, do not hesitate to leave me a comment.


Final configuration of the GitHub action 

1. We come to the final part. Here you just have to put the correct names of your cluster, service, your application, the reference to the task definition in dev and prod, and the use of your AWS credentials. As you can see it will only deploy to production if the deployment to development works. 



# This workflow will build and push a new container image to Amazon ECR,

# and then will deploy a new task definition to Amazon ECS, when there is a push to the "main" branch.

#

# To use this workflow, you will need to complete the following set-up steps:

#

# 1. Create an ECR repository to store your images.

#    For example: `aws ecr create-repository --repository-name my-ecr-repo --region us-east-2`.

#    Replace the value of the `ECR_REPOSITORY` environment variable in the workflow below with your repository's name.

#    Replace the value of the `AWS_REGION` environment variable in the workflow below with your repository's region.

#

# 2. Create an ECS task definition, an ECS cluster, and an ECS service.

#    For example, follow the Getting Started guide on the ECS console:

#      https://us-east-2.console.aws.amazon.com/ecs/home?region=us-east-2#/firstRun

#    Replace the value of the `ECS_SERVICE` environment variable in the workflow below with the name you set for the Amazon ECS service.

#    Replace the value of the `ECS_CLUSTER` environment variable in the workflow below with the name you set for the cluster.

#

# 3. Store your ECS task definition as a JSON file in your repository.

#    The format should follow the output of `aws ecs register-task-definition --generate-cli-skeleton`.

#    Replace the value of the `ECS_TASK_DEFINITION` environment variable in the workflow below with the path to the JSON file.

#    Replace the value of the `CONTAINER_NAME` environment variable in the workflow below with the name of the container

#    in the `containerDefinitions` section of the task definition.

#

# 4. Store an IAM user access key in GitHub Actions secrets named `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.

#    See the documentation for each action used below for the recommended IAM policies for this IAM user,

#    and best practices on handling the access key credentials.


name: Deploy to Amazon ECS


on:

  push:

    branches: [ "main" ]


env:

  AWS_REGION_DEV: eu-central-1                          # set this to your preferred AWS region, e.g. us-west-1

  ECR_REPOSITORY: highchart-export-server           # set this to your Amazon ECR repository name

  ECS_SERVICE: highchartserver-service           # set this to your Amazon ECS service name

  ECS_CLUSTER: HighchartServerCluster               # set this to your Amazon ECS cluster name

  ECS_TASK_DEFINITION_DEV: task-definition-dev.json     # set this to the path to your Amazon ECS task definition

                                                    # file, e.g. .aws/task-definition.json

  

  AWS_REGION_PROD: eu-north-1

  ECS_TASK_DEFINITION_PROD: task-definition.json

  CONTAINER_NAME: hightchartserver-app              # set this to the name of the container in the

                                                    # containerDefinitions section of your task definition

                                                    


permissions:

  contents: read


jobs:

  deploy-to-dev:

    name: Deploy to DevTest

    runs-on: ubuntu-latest

    environment: DevTest


    steps:

    - name: Checkout

      uses: actions/checkout@v3


    - name: Configure AWS credentials for DevTest

      uses: aws-actions/configure-aws-credentials@v2

      with:

        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}

        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

        aws-region: ${{ env.AWS_REGION_DEV }}


    - name: Login to Amazon ECR DevTest

      id: login-ecr

      uses: aws-actions/amazon-ecr-login@v1


    - name: Build, tag, and push image to Amazon ECR DevTest

      id: build-image

      env:

        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}

        IMAGE_TAG: ${{ github.sha }}

      run: |

        # Build a docker container and

        # push it to ECR so that it can

        # be deployed to ECS.

        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .

        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

        echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT


    - name: Fill in the new image ID in the Amazon ECS task definition DevTest

      id: task-def

      uses: aws-actions/amazon-ecs-render-task-definition@v1

      with:

        task-definition: ${{ env.ECS_TASK_DEFINITION_DEV }}

        container-name: ${{ env.CONTAINER_NAME }}

        image: ${{ steps.build-image.outputs.image }}


    - name: Deploy Amazon ECS task definition DevTest

      uses: aws-actions/amazon-ecs-deploy-task-definition@v1

      with:

        task-definition: ${{ steps.task-def.outputs.task-definition }}

        service: ${{ env.ECS_SERVICE }}

        cluster: ${{ env.ECS_CLUSTER }}

        wait-for-service-stability: true


  deploy-to-prod:

    name: Deploy to Production

    needs: [deploy-to-dev]

    runs-on: ubuntu-latest

    environment: Production


    steps:

    - uses: actions/checkout@v3


    - name: Configure AWS credentials for Production

      uses: aws-actions/configure-aws-credentials@v2

      with:

        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}

        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

        aws-region: ${{ env.AWS_REGION_PROD }}


    - name: Login to Amazon ECR Production

      id: login-ecr-prod

      uses: aws-actions/amazon-ecr-login@v1


    - name: Get image ID from Amazon ECR Production (Sync is enabled)

      id: build-image-prod

      env:

        ECR_REGISTRY: ${{ steps.login-ecr-prod.outputs.registry }}

        IMAGE_TAG: ${{ github.sha }}

      run: |

        # Verify Image ID

        echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

    - name: Fill in the new image ID in the Amazon ECS task definition Production

      id: task-def-prod

      uses: aws-actions/amazon-ecs-render-task-definition@v1

      with:

        task-definition: ${{ env.ECS_TASK_DEFINITION_PROD }}

        container-name: ${{ env.CONTAINER_NAME }}

        image: ${{ steps.build-image-prod.outputs.image }}


    - name: Deploy Amazon ECS task definition Production

      uses: aws-actions/amazon-ecs-deploy-task-definition@v1

      with:

        task-definition: ${{ steps.task-def-prod.outputs.task-definition }}

        service: ${{ env.ECS_SERVICE }}

        cluster: ${{ env.ECS_CLUSTER }}


Push your changes and you will see the deployment run.



And remember that if you use renovate bot you can have your project always updated. Check out our article on using renovate bot that we did earlier.


Final Test

To see that everything works correctly you can do a curl or postman to the ELB or URL. If you get the image, you already have a workflow ready with GitHub actions deploying to AWS ECS.



Until next time:  

If you need consultancy or advice to turn your ideas into reality, do not hesitate to contact Exoreaction


Enjoy!


Joe

Share:

Highchart Export Server

 


Render Charts on the server

Server-side generation of charts without the use of a web client. It’s the perfect solution for email and report generation. More information is in this link.

The source code is in Git Hub at https://github.com/highcharts/node-export-server.


Usage Example

The idea is straightforward. You can send a JSON to the node export server's endpoint and get an image or pdf according to the data.


Example of JSON:


{


    "infile": {


        "title": {


            "text": "Steep Chart"


        },


        "xAxis": {


            "categories": [


                "Jan",


                "Feb",


                "Mar"


            ]


        },


        "series": [


            {


                "data": [


                    29.9,


                    71.5,


                    106.4


                ]


            }


        ]


    }


}


Highchart export server as Docker Container

The project exists as an npm package at https://www.npmjs.com/package/highcharts-export-server. But the last version was deployed two years ago. So I will tell you what problems you can find in the process.

1. The project installed from npm presents some issues. So I recommend you better clone the project and make the following changes:


let schema = {

    properties: {

        agree: {

            description: 'Agree to the license terms? y/n',

            required: true,

            default: 'yes',

            message: 'Please enter (y)es or (n)o',

            conform: boolConform

        },

During the installation, it is necessary to accept the terms of the license. With this, you will have already answered yes and it will not give problems at the end of the installation.


2. We create a Dockerfile with this content:


FROM node:carbon


ENV ACCEPT_HIGHCHARTS_LICENSE="1"

ENV HIGHCHARTS_USE_STYLED="1"

ENV HIGHCHARTS_USE_MAPS="1"

ENV HIGHCHARTS_USE_GANTT="1"

ENV HIGHCHARTS_VERSION="10.3.3"

ENV OPENSSL_CONF=/dev/null


#RUN npm install highcharts-export-server -g --unsafe-perm

COPY ./node-export-server /node-export-server

RUN cd node-export-server && npm install && npm link


RUN node /usr/local/lib/node_modules/highcharts-export-server/build.js

RUN npm install


WORKDIR /usr/share/fonts/truetype


ADD fonts/OpenSans-Regular.ttf OpenSans-Regular.ttf

ADD fonts/OpenSans-Light.ttf OpenSans-Light.ttf

ADD fonts/OpenSans-Semibold.ttf OpenSans-Semibold.ttf

ADD fonts/OpenSans-Bold.ttf OpenSans-Bold.ttf

ADD fonts/OpenSans-ExtraBold.ttf OpenSans-ExtraBold.ttf

ADD fonts/OpenSans-Italic.ttf OpenSans-Italic.ttf

ADD fonts/OpenSans-LightItalic.ttf OpenSans-LightItalic.ttf

ADD fonts/OpenSans-BoldItalic.ttf OpenSans-BoldItalic.ttf

ADD fonts/OpenSans-SemiboldItalic.ttf OpenSans-SemiboldItalic.ttf

ADD fonts/OpenSans-ExtraBoldItalic.ttf OpenSans-ExtraBoldItalic.ttf


COPY ./api /api

COPY start.sh /api/start.sh


WORKDIR /api

RUN npm install && chmod +x ./start.sh


EXPOSE 8080

CMD ./start.sh



Unfortunately, the version that exists as an npm package presents many issues as you can find in the project forum. So I recommend you better clone the project and use version 10.3.3.  This version uses phantom js which is already deprecated but still works. New versions have it problem:

The problem stems from the fact that Highcharts v11 now uses ES6 (and Highcharts v10 was still in ES5 standard) so now let and const are used instead of vars. PhantomJS is now deprecated and has no support for standards higher than ES5.

There is a branch in the Github Repo (enhancement/puppeteer) that migrates to puppeteer but doesn't exist good documentation yet and is still in development.  Also with this new branch some templates to generate charts that you find in the examples are not yet supported.

3.  Additionally we need some fonts for the project to work perfectly.


4.  I recommend creating an endpoint for the health check of its service using express.


package.json:

{

  "name": "highcharts-export-server-helthcheck",

  "version": "1.0.0",

  "description": "",

  "main": "index.js",

  "scripts": {

    "test": "echo \"Error: no test specified\" && exit 1"

  },

  "keywords": [],

  "author": "",

  "license": "ISC",

  "dependencies": {

    "express": "^4.17.1",

    "ip": "^1.1.8"

  }

index.js:

let express = require('express');

let app = express();

const IP = require('ip');

const ipAddress = IP.address();

const running_since = new Date().toISOString().

replace(/T/, ' ').      // replace T with a space

replace(/\..+/, '');

app.get('/', (req, res) => {

  res.send("Highchart Export Server works!!")

});


app.get('/health', (req, res) => {



  res.send({"Status": "ok", "Name": "highchart-export-service", "Running_since": running_since, "Version": "1.0.0", "Ip": ipAddress})

});


app.get('/health/', (req, res) => {


  res.send({"Status": "ok", "Name": "highchart-export-service", "Running_since":running_since, "Version": "1.0.0", "Ip": ipAddress})

});

app.get('/', (req, res) => res.send({"status": "ok"}));


console.log('Service start listen in 7000')


app.listen(7000);


5.  Finally the start.sh launch the index.js and highchart export server as two separate processes.

#!/bin/bash


export ACCEPT_HIGHCHARTS_LICENSE=yes


# Start the first process

node index.js &


# Start the second process

#cd ../node-export-server && npm start

highcharts-export-server --enableServer 1 --port 8080 --logLevel 4 &


# Wait for any process to exit

wait -n


# Exit with status of process that exited first

exit $?


 6. Build your docker image:

docker build -t highchart-export-server-mine . 

7. Run your container:

docker run -itd --name highcharts -p 8089:8080  -p 8009:7000 highchart-export-server-mine

8. Test the health check:

❯ curl localhost:8009/health

{"Status":"ok","Name":"highchart-export-service","Running_since":"2023-06-07 05:49:14","Version":"1.0.0","Ip":"172.17.0.2"}%  

9. Test with curl:

 curl -H "Content-Type: application/json" -X POST -d '{"infile":{"title": {"text": "Steep Chart"}, "xAxis": {"categories": ["Jan", "Feb", "Mar"]}, "series": [{"data": [29.9, 71.5, 106.4]}]}}' localhost:8089 -o testchar7.png




Deploying in AWS ECS

In the following article, I will explain how to mount it in AWS ECS 


Thank you for reading this article, if we can help you with anything, do not hesitate to contact us at exoreaction.


Enjoy!


Joe

Share: