顯示具有 nginx 標籤的文章。 顯示所有文章
顯示具有 nginx 標籤的文章。 顯示所有文章

2021年5月18日 星期二

Multi-container restaurant project

Background

A project includes a restaurant owner who wants to deploy our online ordering system with all his restaurants (total 8) to include online ordering features, currently, only a static page goldenthumb.com.hk is running (with https enabled), our target is to use a single AWS lightsail instance to serve for 

1. Providing portal page (run by Gatsby JS), currently goldenthumb.com.hk, act as a portal page to allow user to quickly select different restaurants for ordering, reference site: https://www.maximsmx.com.hk/takeaway_promotion/?utm_source=eatizen

2. Use subdomain to distinguish different restaurants, e.g. lck.goldenthumb.com.hk is the ordering system of restaurant located at Lai Chi Kok

3. api.lck.goldenthumb.com.hk is the API URL served by loopback JS container to provide RESTful service to access database (mongoDB)'s data

4. Run in https and able to check and renew the cert automatically

5. Provide ftp access for the static goldenthumb.com.hk file updates for site maintenance










Problems

This project setup is very much similar to another post, but more complicated, as it includes 2 more containers, certbot and static portal page container which co-exists with the nginx, loopback, mongo express, mongoDB containers (the ordering web application formation) as well as the ftp container and another nginx container.

The following are the problems I have faced during the system configuration

1. The lck.goldenthumb.com.hk requests are not forwarded to the desired nginx container, showing "502 bad gateway"

Turns out I have wrongly restart the container that is never meant to be restarted, normally the static web container "goldenthumbStatic" is used as the main server to receive client requests, the proxy_server settings should be configured inside "goldenthumbStatic" as follows

server {
  # Serving api url for internal use
  server_name lck.goldenthumb.com.hk;
  
  ssl_certificate /etc/letsencrypt/live/goldenthumb.com.hk/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/goldenthumb.com.hk/privkey.pem;
  
  listen 80 ;
  listen 443 ssl ;
  
  # access_log /var/log/nginx/access.loopback.log;
  # We redirect all outside request to appropriate loopback container
  location / {
    proxy_pass http://lck.goldenthumb.com.hk;
  }

  location /admin {
    proxy_pass http://lck.goldenthumb.com.hk;
    try_files $uri /index.html;
  }

  location /.well-known/acme-challenge/ {
    root /var/www/certbot;
  }
}

The location / block is important, it tells the server when request comes as "lck.goldenthumb.com.hk", then forward the request to server block "http://lck.goldenthumb.com.hk", which is defined below 

# Groups of server for proxy_pass usage in below server block
upstream lck.goldenthumb.com.hk {
  # We put docker container service name (internal docker IP) for pointing to right API server (8082:80)
  server frontend;
}

Here the upstream block is used to define blocks of server(s) for reference of proxy_pass directives, the value in proxy_pass is related to this upstream block and forward the request to frontend.

Although configurated correctly, I keep restarting the wrong docker container which the settings are therefore yet to apply 

2. The api.lck.goldenthumb.com.hk requests are not forwarded to the desired loopback container, showing "Connection refused"

This is turn out a silly mistake which forgot to update loopback configuration file (config.json), 80 port should be configured to accept the API connections from other containers

3. The admin management page lck.goldenthumb.com.hk/admin is not working, showing "Connection refused"

The nginx conf is not configured correctly, below is the corrected config, the "try_files" is important to guide nginx server to query original index.html when api.lck.goldenthumb.com.hk/admin is navigated, and let react route to handle the rest of the routing tasks instead of the server itself

  location /admin {
    proxy_pass http://lck.goldenthumb.com.hk;
    try_files $uri /index.html;
  }

4. The APIs used in admin page all fired as "lck.goldenthumb.com.hk/xxx" which is different from expected "api.lck.goldenthumb.com.hk/xxx"

This is due to the wrongly configured dockerfile setup, missing a step to copy the compiled js files to the production docker container, causing the updated code not reflecting the changes.

5. The https certbot challenges for multiple subdomains

HTTPS needs to apply for every sub-domain, lck.goldenthumb.com.hk, api.lck.goldenthumb.com.hk needs to be applied separately, update the certbot script (init-letsencrypt.sh) first, with domain marked to all desired sub-domains

domains=(goldenthumb.com.hk www.goldenthumb.com.hk lck.goldenthumb.com.hk api.lck.goldenthumb.com.hk)

Then in docker compose file, configure the all container volumes which need https certificates by adding /var/www/certbot to accept the challenges

./app/certbot/www:/var/www/certbot


2021年1月11日 星期一

Building web server of multiple docker instances with ssl (https) protection in AWS lightsail

Background

Continue from last tutorial of creating http server with ssl protection, this time I want a bid more advanced, here is the situation.

I have a take away web application, which consists of 5 servers, they are frontend server powered by react, middleware server powered by loopback (nodeJS server), mongoExpress for monitoring mongoDB in UI, mongoDB database and a ftp server for updating the menu and meal information. System architecture is as shown as follow

So there exists 5 docker containers, running in the same local network, I want to put them altogether as a web application to serve my client, but the problem is the SSL cert, I want security transaction and need to register and deploy the certificate for my react front-end and middleware loopback server, so how can I deploy all of them (with same internal 80 port) to the Internet?


Problems

The following are the requirements & problems needed to solve

1. Getting a signed certificate from trusted party

2. Allow "api.goodmaneat.com" to be surfed by front-end server to acquire middleware functionalities through port 443 (https) (which co-exists with the front-end container)

3. Is the cert being shared by *.goodmaneat.com and goodmaneat.com?

4. The following is what needed to achieve

- www.goodmaneat.com -> goodmaneat.com

- www.goodmaneat.com/admin -> goodmaneat.com/admin

- http://goodmaneat.com -> https://goodmaneat.com

So basically, I want the server to strip the www prefix and force redirect to https


Solutions

The below items are what I have done to tackle the problems

1. Create a lightsail AWS instance of type Amazon Linux 2, which is good if you have any service needed to use aws cli

2. Install docker and docker compose to the Amazon Linux (https://gist.github.com/npearce/6f3c7826c7499587f00957fee62f8ee9)

    - Note: Logout / restart the instance after installing or docker cannot function properly

3. Assume you deploy your docker images in AWS, configure your login credential first before accessing Amazon registry (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html)

4. Git clone or upload all files to the server and use docker compose to build up the docker images to containers, I have the following directories for making the whole application functions. Check all 5 containers are up and run.

Docker-compose file reference: (https://docs.google.com/document/d/1SPWOBeLL23E75W_D9U38jZACVNR9jZVwbZqtljIWX2I/edit?usp=sharing)

Note for some important points for the docker-compose yml file

- Loopback container's port setting should be 8082:80 (host:container), we cannot have 80:80 as frontend has already occupied the port 80

- Add 443:443 port settings to frontend container to serve for https connection

- Add nginx and letsencrypt folder to store nginx configuration (which we will deal with in later steps) and certificates (Note: the whole /etc/letsecrypt folder should be mounted for https to work properly)

- Update the dockerFile of the frontend container as the following (https://docs.google.com/document/d/1hJyFyWSazhE_qx9G2dBGc0of9BlDuBgdk0VdKGRRGwk/edit?usp=sharing), here, we install certbox for obtaining certificates

5. docker exec into frontend container, follow step 2 to step 6 to complete the retrieval of certificate process (https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-using-lets-encrypt-certificates-with-wordpress)

Note: 

- Here we assume you already have a domain name registered and have full control as you need to add TXT records to complete the letsencrypt challenges, you should have also configured the route 53 record (assume you are using AWS as your domain name service provider) to add the domain name and IP mapping of "www.goodmaneat.com", "goodmaneat.com" and other sub domain name required to the route 53 record table.

6. Still in frontend container, assuming you are using nginx as web server, head to /etc/nginx/conf.d/nginx.conf (https://docs.google.com/document/d/18LuvdXzsE1qsyBP3fFpP1A1v528MLiYA_Ime-_yptfY/edit?usp=sharing), update the configuration file as the specified URL to 

- Locate the certificate registered in step 5

- Update port settings to only accept https connections

- 301 permanent redirect when www.goodmaneat.com/* is detected, the first sever block configuration accomplish such effect by recognizing domain name "www.goodmaneat.com" and all its subdomains, and return 301 header redirect to its https and www removed URL.

- The second server block serves only https URLs

- The third server block is the most tricky part, it detects server name "api.goodmaneat.com", we still need to provide certificate file here because we accepts only https connection even for internal docker container access. 

- The "proxy_pass" in location block is important, it works with upstream block to guide nginx server when api.goodmaneat.com (http/https) is being accessed, it reverses proxy to send the request to the requested server (this time it is our "loopback container", which can be referred using docker service name), nginx then fetches the response and send it back to our client (frontend container), the upstream block provides group of servers for proxy_pass directive to refer to, e.g.: the value of "proxy_pass http://api.goodmaneat.com" will be parsed as "proxy_pass http://(internal docker IP of loopback container)"

6. After all the nginx configurations, reload the nginx server by "nginx -s reload -c /etc/nginx/conf.d/nginx.conf"


Finally, the file structure of the host server (not the docker container) should be as follows

- Letsencrypt folder volume amount is for storing the certificate and key files in frontend nginx web server container

- nginx-conf folder to map and permanently store the server configuration files in step 5 in frontend nginx web server container









Note

When cert is being updated, some cert file's ownership and user rights will change to root, which makes reading of certificate failed, kindly change to appropriate user and user right before deployment when cert is being renewed


References

- Nginx multiple server blocks listening to same port

- Update: Using Free Let’s Encrypt SSL/TLS Certificates with NGINX

- Multiple docker containers accessible by nginx reverse proxy 

- How to Host Multiple Docker Containers on a Single Droplet with Nginx Reverse Proxy?

- How to proxy_pass to a node docker container on port 80 with nginx container

- How To Redirect HTTP To HTTPS In Nginx

- nginx with Let’s Encrypt in Docker container

- Multiple SSL certificates for a single domain on different servers

- How nginx processes a request

- NGINX multiple server blocks with reverse proxy

- Module ngx_http_upstream_module

- Differences Between A and CNAME Records

- Secure your site with HTTPS

- http directive error in nginx.conf

- Example for a reverse multi-domain proxy using nginx and docker

- Automated nginx proxy for Docker containers using docker-gen

- Using Amazon ECR with the AWS CLI