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


2021年1月1日 星期五

[Apache] Updating http server to https

Background

No one will delay the importance of https, to secure the data transaction between client and server, however in the past when https is not very common in Internet, we developers suffer from registration cost of a certificate and complicated setup of Apache server. One of our client although not specifically request, needs https at all time of their official web site.


Solutions

Steps are not difficult nowadays to complete the https setup, here is my system setup. 

- A LAMP docker image (mattrayner/lamp:latest-1804) https://hub.docker.com/r/mattrayner/lamp which has already setup and work in production.

- Amazon lightsail service for hosting

- A valid hostname registered in hosting speed with full control of the domain name through the domain name panel

Steps

1. Register the SSL certificate (FREE) in letsencrypt (https://letsencrypt.org/) through lightsail web terminal or any ways you can think of accessing the virtual server. Install software-properties-common and certbot accordingly (Details refer to https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-using-lets-encrypt-certificates-with-wordpress)

2. Specify the domain name and the wildcard in the environment variable, use certbot to request Let's Encrypt for the new certificate.

3. Use the following command to start certbot in interactive mode, follow the instruction to complete the registration.

sudo certbot -d $DOMAIN -d $WILDCARD --manual --preferred-challenges dns certonly

Note: There is a process where Let's Encrypt verifies the ownership of the domain, and you as the domain owner needs to add a TXT DNS record to complete the challenge. I am stucked at this as I wrongly follow the unclear instructions of Let's Encrypt's instruction. The TXT record required to fill in your DNS panel is _acme-challenge.example.com with a series of long string, but I wrongly put the whole address in the below table which caused failure of the challenges. It indeed needs only the first part "_acme-challenge" since the .goldenthumb.com.hk has already added for you during the DNS enquiry, so NO NEED to put the whole address to the name field of the DNS record panel.




4. Complete the challenges in the interactive shell and your certificate will be issued. Mark down the directory in which the certificates are stored

5. Update the docker-compose file volumes configuration so that volume is mapped to include the certificate files in the docker container, they will be used as https's certificate afterwards, at the same time, add 443:443 port mapping in ports configuration to allow correct functions of https

6. You can also map the path /etc/apache2/sites-available to local for easy access and configure the apache web server settings

7. Update the 000-default.conf to read the certificate files in step 5, the file sample is as follows

https://drive.google.com/file/d/1usQ9kHb38SyCxW8oqYT1fMAja1_Xj54S/view

The sample configuration includes pointing the certificate files, setting up 443 port based virtual server, redirecting all non https request permenantly to the https URL

8. Update the docker-compose file again, add build configuration and remove image configuration, because we are adding custom commands / scripts to the new yml file

9. Create a new dockerFile with source using the LAMP container (mattrayner/lamp:latest-1804), add the following custom commands in the dockerFile yml

- a2enmod ssl

- service apache2 restart

10. Navigate to lightsail management panel, open the port inbound for port 443 used in https

11. Stop the running containers, rebuild and make the containers up again, your server is now https protected


Certificate Renewal

1. Run "/usr/bin/certbot renew >> /var/log/certbot"

2. Reply the interactive terminal, provide emails, agree terms etc.

3. Add DNS record (TXT) for the challenges  






4. Create a file to accept the second challenge  





5. Restart the web server

Congrats, certificate renewed

References



2020年12月20日 星期日

Dot env file is not found in docker compose file

Background

So there is a case I need to substitue the variables defined in .env to the docker compose for further process, but when I run the docker-compose up command, the variable refer in docker compose file cannot be interpreted.  


Reason

Normally the .env file sits to the same directory as the docker compose yml file, and is expected the docker compose command being executed in the current directory of the docker compose file and the .env file, which I am not in this case.


Solutions

If the current directory is not the place where the compose file is sitting in, we need to specify --project-directory to allow docker-compose program to be able to recognize the directory where the yml file and the .env file sits in, the correct command after fixing is as follows

docker-compose -f {docker-compose-file-dir} --project-directory={directory-of-docker-compose-file} up -d

2020年12月19日 星期六

Setting up custom variables in gitlab pipeline

Background

Having a new pipeline needed to setup, I try to put some frequently use variables e.g. AWS login ID to the custom variable convenient for my pipeline usage, but somehow it fails to read the variable 


Reason

Settings problem, I configure the variable as a protected variable, which only allows protected branches or tags to use only, as the target branch is not configured, the custom variable is not recognized


Solutions

We need to configure our target branch as a protected branch in Settings > Repository > Protected Tags, in that section we can input regular expression for the tags identified as "protected" branch, after that, the custom variable is recognized successfully

Unable to deploy webpage through docker of AWS lightsail instance

Background

So I am trying to build and deploy a very simple LAMP application through docker, but encounters difficulties of repeatedly restarting mySQL server with the following errors


mysqld entered running state exit status 0 expected

It repeatedly restarts and never stops until I turn off the docker services. I am annoying on this because deploying a LAMP based web site through docker should be short and shouldn't take too much time.


Reason

So I finally figure out I can investigate through mySQL log through /var/log/mariadb/mariadb.log, you can determine the exact log path in the my.cnf file located in /etc/my.cnf


And this shows up as 1 of the log

innodb: cannot allocate memory for the buffer pool


Bingo ! So memory insufficient is the cause


Solutions

As I am using AWS Lightsail, the cheapest plan with only 512mb ram, and the remaining RAM is just several MBs, which is totally not enough for running all 2 containers (lamp docker container & ftp).

The I quickly think of swap spaces, which use fast SSD as memory, following URL's guidance, the LAMP docker service can be run successfully 


References

2020年5月21日 星期四

[Linux & cli] Useful Linux command

Details
The following are some useful commands which usually made use and always being forgotten when needed, here is a note down.
  • Edit /etc/fstab to mount new drives (files system, mount point, type, options, dump, pass)
    • /dev/sdb1   /home/jacky/Documents/2tb-hdd   ext4   defaults  0   0
    • mount -av (run from root, show the mount point)
  • Run command and pipe all output to file
    • do_something 2>&1 | tee -a some_file (2>&1 is to direct all stderr to stdout)
  • Clear all text in a text file
    • > someTxtFile.txt
  • Kill process occupied by a known port (e.g. 32769)
    • sudo kill -9 $(sudo lsof -t -i:32769)
References

2020年5月20日 星期三

[Chrome & Debug] Create and debug with test snippets in Chrome

Background
Very often it would be convenient and time consuming to have a fast setup and runnable js snippet playground to quickly test for the logic or debug etc.

Solutions
1. Open chrome
2. Right click "inspect" or press F12 shortcut
3. Navigate to "Sources"
4. At the left hand side panel, click the double right arrow, click "New snippet"
5. A snippet panel pops and you can freely write the test code
6. Press "Ctrl + Enter" to execute