First, let finding what is blue/green deployment.
A blue/green deployment is a deployment strategy in which you create two separate, but identical environments. One environment (blue) is running the current application version and one environment (green) is running the new application version. Using a blue/green deployment strategy increases application availability and reduces deployment risk by simplifying the rollback process if a deployment fails. Once testing has been completed on the green environment, request from client is directed to the green environment and the blue environment is deprecated.
When you deploy out service, it’s usually stay behind a reverse proxy. Nginx, HA Proxy is the popular choice.
But why i do not chose Nginx instead of HA Proxy.
The first reason is Nginx did not allow us to interact with run time easily, the only way is update Nginx configuration and use reload function ( nginx -s reload
), it is hard to populate upstream health status, metrics, route traffic to another upstream on the fly.
HA Proxy is an old product, a 22 years old proxy, come with a lot of feature and allow us to interact with runtime easier. But it will not easy to learn at the first time due to a very long documentation, the official document format is old style,hard to read, but luckily we have another one provide a beautiful documentation. The official blog page still have some introduction for their feature.
Another thing is built-in admin dashboard, it is simple but very useful, provide enough information about the service behind.
So, let take a fast look into documentation before going down, i will show how i usually release my application.
If you have not haproxy installed on current machine, follow the instruction from.
https://github.com/haproxy/haproxy/blob/master/INSTALL
Let have a very simple back end with only one application stand behind a haproxy
service. Proxy server will listen on port 8085
(we will runs directly on host machine ) and two docker application listen on port 3000
, 4000
, it’s just echo server.
Blue application is running and handle traffic come from port 3000 while Green application is stopped.
HA Proxy process start with some arguments and a config file. Let take a look at the below configuration file.
You can see i define a frontend
, backend
section. Front-end section is define which port/protocol haproxy will listen on, in this case, port is 8785
and protocol is http
. In each backend
i define which service belong to this backend, port, health check configuration, you can check these thing on the documentation i listed above.
Beside that in global
section, you can see i define a port 9000
, it’s the port for us to interact with runtime via TCP.
A special point is use_backend
directive in the frontend
section, haproxy allow us create a map (key, value) to store information about which backend will handle the request depends on our configuration, with every imcoming request, haproxy will lookup to this map and find out which back end will handle this request.
I define a key SIMPLE_SERVICE
, the value is the name of the backend
server which will handle request from frontend igw
.
use_backend %[str(SIMPLE_SERVICE),map(/home/dong/code/learn-haproxy/hosts.map)]
A good new is HA Proxy allow us to modify map value with runtime api, this is amazing feature that help us control out proxy.
For example we can call runtime api to forward traffic to another back end if we can, simply edit that map value.
So let start out haproxy process. Remember we have a back end “Blue” is listening for incoming request while “Green” back end is down.
haproxy -W -f haproxy.cfg -p haproxy.pid
After running this command, HA Proxy process was running.
You can ignore some verbose message now, it’s just some event displayed from health checking, dns discovery.
Go to http://localhost:8785/admin to watch the current state. From the configuration, the admin user/password is admin/admin
.
So let send a request to http://localhost:8785
.
From the haproxy stdout
, you can see that “Blue” handled out request.
127.0.0.1:39032 [13/Aug/2022:17:02:40.664] igw blue/blue 0/0/0/1/1 200 689 - - ---- 1/1/0/0/0 0/0 "GET / HTTP/1.1"
Let deploy new version for “Green”, to be simple i just re-create this.
docker compose up -d --build --force-recreate green
Next step, we verify “Green” is up and ready to handle incoming request. You can see on dashboard or send a message to runtime using port 9000.
echo "show stat" | socat stdio tcp4-connect:127.0.0.1:9000
You will received a lot of stat but let choose only status
field.
➜ learn-haproxy echo "show stat" | socat stdio tcp4-connect:127.0.0.1:9000
# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,agent_status,agent_code,agent_duration,check_desc,agent_desc,check_rise,check_fall,check_health,agent_rise,agent_fall,agent_health,addr,cookie,mode,algo,conn_rate,conn_rate_max,conn_tot,intercepted,dcon,dses,wrew,connect,reuse,cache_lookups,cache_hits,srv_icur,src_ilim,qtime_max,ctime_max,rtime_max,ttime_max,eint,idle_conn_cur,safe_conn_cur,used_conn_cur,need_conn_est,uweight,agg_server_check_status,-,ssl_sess,ssl_reused_sess,ssl_failed_handshake,h2_headers_rcvd,h2_data_rcvd,h2_settings_rcvd,h2_rst_stream_rcvd,h2_goaway_rcvd,h2_detected_conn_protocol_errors,h2_detected_strm_protocol_errors,h2_rst_stream_resp,h2_goaway_resp,h2_open_connections,h2_backend_open_streams,h2_total_connections,h2_backend_total_streams,h1_open_connections,h1_open_streams,h1_total_connections,h1_total_streams,h1_bytes_in,h1_bytes_out,h1_spliced_bytes_in,h1_spliced_bytes_out,
igw,FRONTEND,,,0,3,8192,3,243,2067,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,1,,,,0,3,0,0,0,0,,0,1,3,,,0,0,0,0,,,,,,,,,,,,,,,,,,,,,http,,0,1,3,0,0,0,0,,,0,0,,,,,,,0,,,,,,,-,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,234,2082,0,0,
blue,blue,0,0,0,1,10,3,243,2067,,0,,0,0,0,0,UP,1,1,0,0,1,547,0,,1,3,1,,3,,2,0,,1,L7OK,200,2,0,3,0,0,0,0,,,,3,0,0,,,,,497,,,0,0,1,1,,,,Layer7 check passed,,2,2,3,,,,[::1]:3000,,http,,,,,,,,0,3,0,,,0,,0,0,1,1,0,0,0,0,1,1,,-,0,0,0,,,,,,,,,,,,,,,,,,,,,,
blue,BACKEND,0,0,0,1,1,3,243,2067,0,0,,0,0,0,0,UP,1,1,0,,1,552,0,,1,3,0,,3,,1,0,,1,,,,0,3,0,0,0,0,,,,3,0,0,0,0,0,0,497,,,0,0,1,1,,,,,,,,,,,,,,http,roundrobin,,,,,,,0,3,0,0,0,,,0,0,1,1,0,,,,,1,0,-,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,278,164954,10493,0,0,
green,green,0,0,0,0,10,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,1,2,551,551,,1,4,1,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,,,,0,0,0,,,,,-1,Connection refused,,0,0,0,0,,,,Layer4 connection problem,,2,2,0,,,,[::1]:4000,,http,,,,,,,,0,0,0,,,0,,0,0,0,0,0,0,0,0,1,1,,-,0,0,0,,,,,,,,,,,,,,,,,,,,,,
green,BACKEND,0,0,0,0,1,0,0,0,0,0,,0,0,0,0,DOWN,0,0,0,,2,551,551,,1,4,0,,0,,1,0,,0,,,,0,0,0,0,0,0,,,,0,0,0,0,0,0,0,-1,,,0,0,0,0,,,,,,,,,,,,,,http,roundrobin,,,,,,,0,0,0,0,0,,,0,0,0,0,0,,,,,0,0,-,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,276,276,0,0,0,0,
DEFAULT,BACKEND,0,0,0,0,820,0,0,0,0,0,,0,0,0,0,UP,0,0,0,,0,552,,,1,5,0,,0,,1,0,,0,,,,0,0,0,0,0,0,,,,0,0,0,0,0,0,0,-1,,,0,0,0,0,,,,,,,,,,,,,,http,roundrobin,,,,,,,0,0,0,0,0,,,0,0,0,0,0,,,,,0,0,-,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
Only get status field from output.
➜ learn-haproxy echo "show stat" | socat stdio tcp4-connect:127.0.0.1:9000 | cut -d "," -f 2,18 | column -s, -t | grep green | awk "{print \$2}"
DOWN
➜ learn-haproxy
Wait a bit, after “Green” passed health check, you will see it UP
.
➜ learn-haproxy echo "show stat" | socat stdio tcp4-connect:127.0.0.1:9000 | cut -d "," -f 2,18 | column -s, -t | grep green | awk "{print \$2}"
UP
➜ learn-haproxy
Next step, forward all incoming traffic to Green
.
echo "set map /home/dong/code/learn-haproxy/hosts.map SIMPLE_SERVICE green" | socat stdio tcp4-connect:127.0.0.1:9000
Send a request to http://localhost:8785
and see haproxy stdout, you will see Green
handle out request.
127.0.0.1:39054 [13/Aug/2022:17:16:15.091] igw green/green 0/0/0/2/2 200 689 - - ---- 3/3/0/0/0 0/0 "GET / HTTP/1.1"
So let write a script, we will verify health status up green
when deploying it, wait green
to up then forwarding traffic to green
.
The waitItUp
function receive server name from argument( green
or blue
), call runtime to make sure it is UP
then stop.
The forwardTo
function receive server name from argument( green
or blue
), call runtime to set map value, so haproxy will move traffic to another back end.
function forwardTo(){
echo "Forwarding traffic to $1"
echo "set map /home/dong/code/learn-haproxy/hosts.map SIMPLE_SERVICE $1" | socat stdio tcp4-connect:127.0.0.1:9000
}
The readyToDown
function receive server name from argument( green
or blue
), call runtime to populate the current connection count, if there is 0, we can safely stop this backend.
function readyToDown(){
serverName=$1
ok="false"
while [[ $ok == "false" ]];
do
sleep 1
count=$(echo "show servers conn $serverName $" | socat stdio tcp4-connect:127.0.0.1:9000 | sed -n 2p | cut -d " " -f 7)
echo "current connection on $serverName: ${count}"
if [[ $count == "0" ]]; then
ok="true"
fi;
done
echo "Shutdown server: $serverName"
}
Wrap them up into a file called deploy.sh
To release a application.
./deploy.sh release
To rollback deployment
./deploy.sh rollback
To confirm new version is fine, deploy new version to blue and forward all traffic back end blue, run
./deploy.sh done
We can try to send request while deploying our service, the hey
tool is perfect for this purpose.
Ready to test our release process.
Start haproxy process, blue container, stop green container.
Start with hey
hey -n 100000 -c 100 http://localhost:8785/
Run release command
./deploy.sh release
After the test is done
You can see that, there is no request with error, we make a zero downtime
deployment.
Thank you for reading.