
Build a UGC Live Streaming App with Amazon IVS: Architecture Overview (Lesson 1.4)
Welcome to Lesson 1.4 in this series where we're looking at building a web based user-generated content live streaming application with Amazon IVS. This entire series is available in video format on the AWS Developers YouTube channel and all of the code related to the sample application used in this series can be viewed on GitHub. Refer to the links at the end of the post for more information.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const vpc = new Vpc(this, 'Vpc', {
vpcName: 'streamcat-vpc',
maxAzs: 3,
subnetConfiguration: [
{
cidrMask: 24,
name: 'private-subnet',
subnetType: SubnetType.PRIVATE_WITH_EGRESS,
},
{
cidrMask: 24,
name: 'private-isolated-subnet',
subnetType: SubnetType.PRIVATE_ISOLATED,
},
{
cidrMask: 24,
name: 'public-subnet',
subnetType: SubnetType.PUBLIC,
}
],
});
1
2
3
4
5
vpc.addInterfaceEndpoint('secrets-manager-endpoint', {
service: InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
privateDnsEnabled: true,
subnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const securityGroup = new SecurityGroup(this, 'VpcSecurityGroup', {
securityGroupName: 'streamcat-vpc-security-group',
vpc: vpc,
});
securityGroup.addIngressRule(
Peer.ipv4(vpc.vpcCidrBlock),
Port.tcp(5432),
'Allow port 5432 from within the VPC'
);
securityGroup.addIngressRule(
Peer.anyIpv4(),
Port.tcp(22),
'Allow SSH'
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const dbInstance = new DatabaseInstance(this, 'PostgresInstance', {
vpc: vpc,
vpcSubnets: { subnetType: SubnetType.PRIVATE_ISOLATED },
instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.MICRO),
engine: DatabaseInstanceEngine.postgres({
version: PostgresEngineVersion.VER_14_7,
}),
port: 5432,
securityGroups: [securityGroup],
instanceIdentifier: 'streamcat-test-db',
databaseName: 'streamcat',
credentials: Credentials.fromSecret(dbCredentialsSecret),
backupRetention: cdk.Duration.days(0),
deleteAutomatedBackups: true,
});
1
2
3
4
5
6
7
8
const ivsChatLogConfig = new CfnLoggingConfiguration(this, 'IvsChatLogConfig', {
name: 'streamcat-chat-logging-config',
destinationConfiguration: {
cloudWatchLogs: {
logGroupName: chatLogGroup.logGroupName
}
}
});
1
2
3
4
const chatLogGroup = new LogGroup(this, 'ChatLogGroup', {
logGroupName: 'streamcat-chat-log-group',
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const ivsRecordingConfig = new CfnRecordingConfiguration(this, 'IvsRecordingConfiguration', {
name: 'streamcat-recording-config',
recordingReconnectWindowSeconds: 60,
destinationConfiguration: {
s3: {
bucketName: vodBucket.bucketName,
}
},
thumbnailConfiguration: {
recordingMode: 'INTERVAL',
storage: ['LATEST'],
targetIntervalSeconds: 5,
}
});
1
2
3
4
5
6
7
8
9
10
11
12
const vodBucket = new s3.Bucket(this, 'VodStorageBucket', {
bucketName: 'streamcat-vod-storage',
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
cors: [{
allowedMethods: [
s3.HttpMethods.GET,
],
allowedOrigins: ['*'],
allowedHeaders: ['*'],
}],
});
1
2
3
4
5
6
const vodCfDistribution: Distribution = new Distribution(this, "VodCfDistribution", {
defaultBehavior: {
origin: new S3Origin(vodBucket),
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
});
1
2
3
4
5
6
7
8
9
const moderateChatHandler = new Function(this, 'ModerateIvsChatHandler', {
runtime: Runtime.NODEJS_18_X,
code: Code.fromAsset('resources'),
handler: 'index.moderateChat',
layers: [lambdaLayer],
});
moderateChatHandler.addPermission('PermitModerateChatInvoke', {
principal: new ServicePrincipal('ivschat.amazonaws.com'),
});
1
2
3
4
5
6
7
8
9
10
11
12
13
const streamStateChangeEventRule = new Rule(this, 'StreamStateChangeEventRule', {
description: 'Rule to handle IVS Stream State Changes',
ruleName: 'streamcat-stream-change',
eventPattern: {
source: ['aws.ivs'],
detailType: ['IVS Stream State Change', 'IVS Recording State Change'],
},
targets: [new LambdaFunction(streamStateChangeHandler)],
});
streamStateChangeHandler.addPermission('PermitStreamStateInvoke', {
principal: new ServicePrincipal('events.amazonaws.com'),
sourceArn: streamStateChangeEventRule.ruleArn,
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const lambdaLayer = new LayerVersion(this, 'LambdaLayer', {
layerVersionName: 'LambdaLayer',
compatibleRuntimes: [
Runtime.NODEJS_18_X,
],
code: Code.fromAsset('./resources/dependencies/nodejs'),
});
// refer to https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html#ps-integration-lambda-extensions-add
// for the proper ARN
const secretsAndParametersLayer = LayerVersion.fromLayerVersionArn(this, 'SecretsAndParametersLayer',
'arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:4'
);
const streamStateChangeHandler = new Function(this, 'StreamStateChangeHandler', {
vpc: vpc,
vpcSubnets: {
subnetType: SubnetType.PRIVATE_WITH_EGRESS,
},
runtime: Runtime.NODEJS_18_X,
code: Code.fromAsset('resources'),
handler: 'index.streamStateChanged',
environment: {
PARAMETERS_SECRETS_EXTENSION_LOG_LEVEL: 'info',
PARAMETERS_SECRETS_EXTENSION_CACHE_ENABLED: 'true',
SECRET_NAME: '/streamcat/db-creds',
},
layers: [lambdaLayer, secretsAndParametersLayer],
architecture: Architecture.ARM_64,
timeout: cdk.Duration.seconds(5),
});
const streamStateChangePolicy = new PolicyStatement({
effect: Effect.ALLOW,
resources: ['*'],
actions: ['secretsmanager:GetSecretValue', 'logs:FilterLogEvents'],
});
streamStateChangeHandler.addToRolePolicy(streamStateChangePolicy);
1
2
3
4
5
6
7
8
9
10
11
const bastionHostInstance = new Instance(this, 'BastionHost', {
vpc: vpc,
vpcSubnets: {
subnetType: SubnetType.PUBLIC,
},
securityGroup: securityGroup,
instanceName: 'streamcat-bastion-host',
instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.MICRO),
machineImage: MachineImage.latestAmazonLinux2(),
keyName: 'id_aws',
});
Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.