constructor()

in lib/cdk-grafana-stack.ts [13:184]


  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Get Context Values
    const domainName = this.node.tryGetContext('domainName');
    const hostedZoneId = this.node.tryGetContext('hostedZoneId');
    const zoneName = this.node.tryGetContext('zoneName');

    if (!domainName || !hostedZoneId || !zoneName) {
      throw new Error('Please provide required parameters domainName, hostedZoneId, zoneName via context variables');
    }

    const enablePrivateLink = this.node.tryGetContext('enablePrivateLink');
    const domainZone = r53.PublicHostedZone.fromHostedZoneAttributes( this, "MyHostedZone", {
      hostedZoneId: hostedZoneId,
      zoneName: zoneName
    });
    // vpc
    const vpc = new ec2.Vpc(this, "MyVpc", {
      maxAzs: 2 // Default is all AZs in region
    });

    if (enablePrivateLink == 'true') {
      vpc.addInterfaceEndpoint('CWEndpoint',  {service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH});
      vpc.addInterfaceEndpoint('EFSEndpoint', {service: ec2.InterfaceVpcEndpointAwsService.ELASTIC_FILESYSTEM});
      vpc.addInterfaceEndpoint('SMEndpoint',  {service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER});
    }

    const cluster = new ecs.Cluster(this, "MyCluster", {
      vpc: vpc
    });

    // EFS
    const fileSystem = new efs.FileSystem(this, 'EfsFileSystem', {
      vpc: vpc,
      encrypted: true,
      lifecyclePolicy: efs.LifecyclePolicy.AFTER_14_DAYS,
      performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
      throughputMode: efs.ThroughputMode.BURSTING
    });

    const accessPoint = new efs.AccessPoint(this, 'EfsAccessPoint', {
      fileSystem: fileSystem,
      path: '/var/lib/grafana',
      posixUser: {
        gid: '1000',
        uid: '1000'
      },
      createAcl: {
        ownerGid: '1000',
        ownerUid: '1000',
        permissions: '755'
      }
    });

    // task log group
    const logGroup = new logs.LogGroup(this, 'taskLogGroup', {
      retention: logs.RetentionDays.ONE_MONTH
    });

    // container log driver
    const containerLogDriver = ecs.LogDrivers.awsLogs({
      streamPrefix: 'fargate-grafana', //cdk.Stack.stackName,
      logGroup: logGroup
    });

    // task Role
    const taskRole = new iam.Role(this, 'taskRole', {
      assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
    });

    taskRole.addToPolicy(new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: [
        'cloudwatch:DescribeAlarmsForMetric',
        'cloudwatch:DescribeAlarmHistory',
        'cloudwatch:DescribeAlarms',
        'cloudwatch:ListMetrics',
        'cloudwatch:GetMetricStatistics',
        'cloudwatch:GetMetricData',
        'ec2:DescribeTags',
        'ec2:DescribeInstances',
        'ec2:DescribeRegions',
        'tag:GetResources'
      ],
      resources: ['*']
    }));

    // execution Role
    const executionRole = new iam.Role(this, 'executionRole', {
      assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
    });

    executionRole.addToPolicy(new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: [
        'logs:CreateLogStream',
        'logs:PutLogEvents',
      ],
      resources: [
        logGroup.logGroupArn
      ]
    }));

    // Create Task Definition - # EFS integration currently uses escape hatches until native CDK support is added #
    const volumeName = 'efsGrafanaVolume';

    const volumeConfig: ecs.Volume = {
      name: volumeName,
      efsVolumeConfiguration: {
        fileSystemId: fileSystem.fileSystemId,
        transitEncryption: 'ENABLED',
        authorizationConfig: { accessPointId: accessPoint.accessPointId}
      },
    };

    // https://aws.amazon.com/blogs/aws/amazon-ecs-supports-efs/
    const task_definition = new ecs.FargateTaskDefinition(this, "TaskDef",{
      taskRole: taskRole,
      executionRole: executionRole,
      volumes: [volumeConfig]
    });

    // Grafana Admin Password
    const grafanaAdminPassword = new secretsmanager.Secret(this, 'grafanaAdminPassword');
    // Allow Task to access Grafana Admin Password
    grafanaAdminPassword.grantRead(taskRole);

    // Web Container
    const container_web = task_definition.addContainer("web", {
          image: ecs.ContainerImage.fromRegistry('grafana/grafana'),
          logging: containerLogDriver,
          secrets: {
            GF_SECURITY_ADMIN_PASSWORD: ecs.Secret.fromSecretsManager(grafanaAdminPassword)
          },
          environment: {
            'GF_SERVER_ROOT_URL' : `https://${domainZone.zoneName}`,
          }

        }
    );
    // set port mapping
    container_web.addPortMappings({
      containerPort: 3000
    });
    container_web.addMountPoints({
      sourceVolume: volumeConfig.name,
      containerPath: '/var/lib/grafana',
      readOnly: false
    });

    // Create a load-balanced Fargate service and make it public
    const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, "MyFargateService", {
      domainName: domainName,
      domainZone: domainZone,
      cluster: cluster, // Required
      cpu: 1024, // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html
      desiredCount: 1, // Should be set to 1 to prevent multiple tasks attempting to write to EFS volume concurrently
      taskDefinition: task_definition,
      memoryLimitMiB: 2048, // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html
      protocol: ApplicationProtocol.HTTPS,
      platformVersion: ecs.FargatePlatformVersion.VERSION1_4
    });

    fargateService.targetGroup.configureHealthCheck({
      path: '/api/health'
    });

    // Allow Task to access EFS
    fileSystem.connections.allowDefaultPortFrom(fargateService.service.connections);

  }