Running APISIX in AWS with AWS CDK

APISIX is a cloud-native microservices API gateway, delivering the ultimate performance, security, open source and scalable platform for all your APIs and microservices.

Architecture

This reference architecture walks you through building APISIX as a serverless container API Gateway on top of AWS Fargate with AWS CDK.

Apache APISIX Serverless Architecture

Generate an AWS CDK project with projen

  1. $ mkdir apisix-aws
  2. $ cd $_
  3. $ npx projen new awscdk-app-ts

update the .projenrc.js with the following content:

  1. const { AwsCdkTypeScriptApp } = require('projen');
  2. const project = new AwsCdkTypeScriptApp({
  3. cdkVersion: "1.70.0",
  4. name: "apisix-aws",
  5. cdkDependencies: [
  6. '@aws-cdk/aws-ec2',
  7. '@aws-cdk/aws-ecs',
  8. '@aws-cdk/aws-ecs-patterns',
  9. ]
  10. });
  11. project.synth();

update the project:

  1. $ npx projen

update src/main.ts

  1. import * as cdk from '@aws-cdk/core';
  2. import { Vpc, Port } from '@aws-cdk/aws-ec2';
  3. import { Cluster, ContainerImage, TaskDefinition, Compatibility } from '@aws-cdk/aws-ecs';
  4. import { ApplicationLoadBalancedFargateService, NetworkLoadBalancedFargateService } from '@aws-cdk/aws-ecs-patterns';
  5. export class ApiSixStack extends cdk.Stack {
  6. constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
  7. super(scope, id, props);
  8. const vpc = Vpc.fromLookup(this, 'VPC', {
  9. isDefault: true
  10. })
  11. const cluster = new Cluster(this, 'Cluster', {
  12. vpc
  13. })
  14. /**
  15. * ApiSix service
  16. */
  17. const taskDefinition = new TaskDefinition(this, 'TaskApiSix', {
  18. compatibility: Compatibility.FARGATE,
  19. memoryMiB: '512',
  20. cpu: '256'
  21. })
  22. taskDefinition
  23. .addContainer('apisix', {
  24. image: ContainerImage.fromRegistry('iresty/apisix'),
  25. })
  26. .addPortMappings({
  27. containerPort: 9080
  28. })
  29. taskDefinition
  30. .addContainer('etcd', {
  31. image: ContainerImage.fromRegistry('gcr.azk8s.cn/etcd-development/etcd:v3.3.12'),
  32. // image: ContainerImage.fromRegistry('gcr.io/etcd-development/etcd:v3.3.12'),
  33. })
  34. .addPortMappings({
  35. containerPort: 2379
  36. })
  37. const svc = new ApplicationLoadBalancedFargateService(this, 'ApiSixService', {
  38. cluster,
  39. taskDefinition,
  40. })
  41. svc.targetGroup.setAttribute('deregistration_delay.timeout_seconds', '30')
  42. svc.targetGroup.configureHealthCheck({
  43. interval: cdk.Duration.seconds(5),
  44. healthyHttpCodes: '404',
  45. healthyThresholdCount: 2,
  46. unhealthyThresholdCount: 3,
  47. timeout: cdk.Duration.seconds(4)
  48. })
  49. /**
  50. * PHP service
  51. */
  52. const taskDefinitionPHP = new TaskDefinition(this, 'TaskPHP', {
  53. compatibility: Compatibility.FARGATE,
  54. memoryMiB: '512',
  55. cpu: '256'
  56. })
  57. taskDefinitionPHP
  58. .addContainer('php', {
  59. image: ContainerImage.fromRegistry('abiosoft/caddy:php'),
  60. })
  61. .addPortMappings({
  62. containerPort: 2015
  63. })
  64. const svcPHP = new NetworkLoadBalancedFargateService(this, 'PhpService', {
  65. cluster,
  66. taskDefinition: taskDefinitionPHP,
  67. assignPublicIp: true,
  68. })
  69. // allow Fargate task behind NLB to accept all traffic
  70. svcPHP.service.connections.allowFromAnyIpv4(Port.tcp(2015))
  71. svcPHP.targetGroup.setAttribute('deregistration_delay.timeout_seconds', '30')
  72. svcPHP.loadBalancer.setAttribute('load_balancing.cross_zone.enabled', 'true')
  73. new cdk.CfnOutput(this, 'ApiSixDashboardURL', {
  74. value: `http://${svc.loadBalancer.loadBalancerDnsName}/apisix/dashboard/`
  75. })
  76. }
  77. }
  78. const devEnv = {
  79. account: process.env.CDK_DEFAULT_ACCOUNT,
  80. region: process.env.CDK_DEFAULT_REGION,
  81. };
  82. const app = new cdk.App();
  83. new ApiSixStack(app, 'apisix-stack-dev', { env: devEnv });
  84. app.synth();

Deploy the APISIX Stack with AWS CDK

  1. $ cdk diff
  2. $ cdk deploy

On deployment complete, some outputs will be returned:

  1. Outputs:
  2. apiSix.PhpServiceLoadBalancerDNS5E5BAB1B = apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
  3. apiSix.ApiSixDashboardURL = http://apiSi-ApiSi-1TM103DN35GRY-1477666967.us-west-2.elb.amazonaws.com/apisix/dashboard/
  4. apiSix.ApiSixServiceLoadBalancerDNSD4E5B8CB = apiSi-ApiSi-1TM103DN35GRY-1477666967.us-west-2.elb.amazonaws.com
  5. apiSix.ApiSixServiceServiceURLF6EC7872 = http://apiSi-ApiSi-1TM103DN35GRY-1477666967.us-west-2.elb.amazonaws.com

Open the apiSix.ApiSixDashboardURL from your browser and you will see the login prompt.

Configure the upstream nodes

All upstream nodes are running as AWS Fargate tasks and registered to the NLB(Network Load Balancer) exposing multiple static IP addresses. We can query the IP addresses by nslookup the apiSix.PhpServiceLoadBalancerDNS5E5BAB1B like this:

  1. $ nslookup apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
  2. Server: 192.168.31.1
  3. Address: 192.168.31.1#53
  4. Non-authoritative answer:
  5. Name: apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
  6. Address: 44.224.124.213
  7. Name: apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
  8. Address: 18.236.43.167
  9. Name: apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
  10. Address: 35.164.164.178
  11. Name: apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
  12. Address: 44.226.102.63

Configure the IP addresses returned as your upstream nodes in your APISIX dashboard followed by the Services and Routes configuration. Let’s say we have a /index.php as the URI for the first route for our first Service from the Upstream IP addresses.

upstream with AWS NLB IP addresses

service with created upstream

define route with service and uri

Validation

OK. Let’s test the /index.php on {apiSix.ApiSixServiceServiceURL}/index.php

Testing Apache APISIX on AWS Fargate

Now we have been successfully running APISIX in AWS Fargate as serverless container API Gateway service.

Clean up

  1. $ cdk destroy

Running APISIX in AWS China Regions

update src/main.ts

  1. taskDefinition
  2. .addContainer('etcd', {
  3. image: ContainerImage.fromRegistry('gcr.azk8s.cn/etcd-development/etcd:v3.3.12'),
  4. // image: ContainerImage.fromRegistry('gcr.io/etcd-development/etcd:v3.3.12'),
  5. })
  6. .addPortMappings({
  7. containerPort: 2379
  8. })

(read here for more reference)

Run cdk deploy and specify your preferred AWS region in China.

  1. # let's say we have another AWS_PROFILE for China regions called 'cn'
  2. # make sure you have aws configure --profile=cn properly.
  3. #
  4. # deploy to NingXia region
  5. $ cdk deploy --profile cn -c region=cn-northwest-1
  6. # deploy to Beijing region
  7. $ cdk deploy --profile cn -c region=cn-north-1

In the following case, we got the Outputs returned for AWS Ningxia region(cn-northwest-1):

  1. Outputs:
  2. apiSix.PhpServiceLoadBalancerDNS5E5BAB1B = apiSi-PhpSe-1760FFS3K7TXH-562fa1f7f642ec24.elb.cn-northwest-1.amazonaws.com.cn
  3. apiSix.ApiSixDashboardURL = http://apiSi-ApiSi-123HOROQKWZKA-1268325233.cn-northwest-1.elb.amazonaws.com.cn/apisix/dashboard/
  4. apiSix.ApiSixServiceLoadBalancerDNSD4E5B8CB = apiSi-ApiSi-123HOROQKWZKA-1268325233.cn-northwest-1.elb.amazonaws.com.cn
  5. apiSix.ApiSixServiceServiceURLF6EC7872 = http://apiSi-ApiSi-123HOROQKWZKA-1268325233.cn-northwest-1.elb.amazonaws.com.cn

Open the apiSix.ApiSixDashboardURL URL and log in to configure your APISIX in AWS China region.

TBD

Decouple APISIX and etcd3 on AWS

For high availability and state consistency consideration, you might be interested to decouple the etcd3 as a separate cluster from APISIX not only for performance but also high availability and fault tolerance yet with highly reliable state consistency.

TBD