AWSTemplateFormatVersion: "2010-09-09" Parameters: CertificateArn: Type: String AllowedPattern: '^.+$' Description: ARN of an existing certificate which will be attached to the ALB created by the stack, to serve HTTPS traffic (mandatory). ClusterName: Type: String Default: default-cluster AllowedPattern: '^.+$' JenkinsJNLPPort: Type: Number Default: 50000 JenkinsUsername: Type: String Default: developer AllowedPattern: '^.+$' JenkinsURL: Type: String AllowedPattern: '^.+$' Description: Public URL of your Jenkins instance e.g. https://jenkins.tomgregory.com (mandatory). Description: Provision the required resources for blog post example 'Deploying Jenkins to ECS'. Wait for creation to complete before testing. Resources: VPCStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: https://tomgregory-cloudformation-examples.s3-eu-west-1.amazonaws.com/default-vpc.yml LoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Subnets: - !GetAtt VPCStack.Outputs.PublicSubnet1 - !GetAtt VPCStack.Outputs.PublicSubnet2 SecurityGroups: - !Ref LoadBalancerSecurityGroup LoadBalancerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: LoadBalancerSecurityGroup GroupDescription: Security group for load balancer VpcId: !GetAtt VPCStack.Outputs.VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 SecurityGroupEgress: - IpProtocol: tcp FromPort: 8080 ToPort: 8080 DestinationSecurityGroupId: !Ref JenkinsSecurityGroup LoadBalancerListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: Certificates: - CertificateArn: !Ref CertificateArn DefaultActions: - Type: forward ForwardConfig: TargetGroups: - TargetGroupArn: !Ref JenkinsTargetGroup LoadBalancerArn: !Ref LoadBalancer Port: 443 Protocol: HTTPS JenkinsTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckPath: /login Name: JenkinsTargetGroup Port: 8080 Protocol: HTTP TargetType: ip VpcId: !GetAtt VPCStack.Outputs.VPC TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 10 JenkinsSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: JenkinsSecurityGroup GroupDescription: Security group for Jenkins VpcId: !GetAtt VPCStack.Outputs.VPC JenkinsLoadBalancerSecurityGroupIngress: Type: AWS::EC2::SecurityGroupIngress Properties: IpProtocol: tcp FromPort: 8080 ToPort: 8080 GroupId: !Ref JenkinsSecurityGroup SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup JenkinsJenkinsAgentSecurityGroupIngress: Type: AWS::EC2::SecurityGroupIngress Properties: IpProtocol: tcp FromPort: !Ref JenkinsJNLPPort ToPort: !Ref JenkinsJNLPPort GroupId: !Ref JenkinsSecurityGroup SourceSecurityGroupId: !Ref JenkinsAgentSecurityGroup ECSCluster: Type: AWS::ECS::Cluster Properties: ClusterName: !Ref ClusterName JenkinsExecutionRole: Type: AWS::IAM::Role Properties: RoleName: jenkins-execution-role Path: / AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: ecs-tasks.amazonaws.com ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy Policies: - PolicyName: secretaccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - secretsmanager:GetSecretValue Resource: !Ref JenkinsPasswordSecret JenkinsRole: Type: AWS::IAM::Role Properties: RoleName: jenkins-role Path: / AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - elasticfilesystem:ClientMount - elasticfilesystem:ClientWrite Resource: !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:file-system/${FileSystemResource} - PolicyName: create-jenkins-agents PolicyDocument: Version: '2012-10-17' Statement: - Action: - ecs:RegisterTaskDefinition - ecs:ListClusters - ecs:DescribeContainerInstances - ecs:ListTaskDefinitions - ecs:DescribeTaskDefinition - ecs:DeregisterTaskDefinition Effect: Allow Resource: '*' - Action: - ecs:ListContainerInstances Effect: Allow Resource: - !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName} - Action: - ecs:RunTask Effect: Allow Condition: ArnEquals: ecs:cluster: - !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName} Resource: !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:task-definition/* - Action: - ecs:StopTask Effect: Allow Condition: ArnEquals: ecs:cluster: - !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName} Resource: !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:task/* - Action: - ecs:DescribeTasks Effect: Allow Condition: ArnEquals: ecs:cluster: - !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName} Resource: !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:task/* - Action: - iam:GetRole - iam:PassRole Effect: Allow Resource: !GetAtt JenkinsExecutionRole.Arn JenkinsTaskDefinition: Type: AWS::ECS::TaskDefinition Properties: Family: !Sub jenkins-task Cpu: 512 Memory: 1024 NetworkMode: awsvpc TaskRoleArn: !Ref JenkinsRole ExecutionRoleArn: !Ref JenkinsExecutionRole RequiresCompatibilities: - FARGATE - EC2 ContainerDefinitions: - Name: jenkins Image: tkgregory/jenkins-ecs-agents:latest PortMappings: - ContainerPort: 8080 - ContainerPort: !Ref JenkinsJNLPPort MountPoints: - SourceVolume: jenkins-home ContainerPath: /var/jenkins_home LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref CloudwatchLogsGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: jenkins Environment: - Name: AGENT_EXECUTION_ROLE_ARN Value: !GetAtt JenkinsExecutionRole.Arn - Name: AGENT_SECURITY_GROUP_ID Value: !Ref JenkinsAgentSecurityGroup - Name: AWS_REGION Value: !Ref AWS::Region - Name: ECS_AGENT_CLUSTER Value: !Ref ClusterName - Name: JENKINS_URL Value: !Ref JenkinsURL - Name: LOG_GROUP_NAME Value: !Ref CloudwatchLogsGroup - Name: PRIVATE_JENKINS_HOST_AND_PORT Value: !Join - '' - - !GetAtt DiscoveryService.Name - '.' - !Ref AWS::StackName - :50000 - Name: SUBNET_IDS Value: !Join - '' - - !GetAtt VPCStack.Outputs.PrivateSubnet1 - ',' - !GetAtt VPCStack.Outputs.PrivateSubnet2 - Name: JENKINS_USERNAME Value: !Ref JenkinsUsername Secrets: - Name: JENKINS_PASSWORD ValueFrom: !Ref JenkinsPasswordSecret Volumes: - Name: jenkins-home EFSVolumeConfiguration: FilesystemId: !Ref FileSystemResource TransitEncryption: ENABLED AuthorizationConfig: AccessPointId: !Ref AccessPointResource IAM: ENABLED CloudwatchLogsGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Join ['-', [ECSLogGroup, !Ref 'AWS::StackName']] RetentionInDays: 14 JenkinsService: Type: AWS::ECS::Service DependsOn: LoadBalancerListener Properties: Cluster: !Ref ECSCluster TaskDefinition: !Ref JenkinsTaskDefinition DesiredCount: 1 HealthCheckGracePeriodSeconds: 300 LaunchType: FARGATE PlatformVersion: 1.4.0 DeploymentConfiguration: MinimumHealthyPercent: 0 MaximumPercent: 100 NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: ENABLED Subnets: - !GetAtt VPCStack.Outputs.PrivateSubnet1 - !GetAtt VPCStack.Outputs.PrivateSubnet2 SecurityGroups: - !GetAtt JenkinsSecurityGroup.GroupId LoadBalancers: - ContainerName: jenkins ContainerPort: 8080 TargetGroupArn: !Ref JenkinsTargetGroup ServiceRegistries: - RegistryArn: !GetAtt DiscoveryService.Arn Port: !Ref JenkinsJNLPPort EFSSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: VpcId: !GetAtt VPCStack.Outputs.VPC GroupDescription: Enable EFS access via port 2049 SecurityGroupIngress: - IpProtocol: tcp FromPort: 2049 ToPort: 2049 SourceSecurityGroupId: !Ref JenkinsSecurityGroup FileSystemResource: Type: AWS::EFS::FileSystem Properties: Encrypted: true FileSystemTags: - Key: Name Value: jenkins-home MountTargetResource1: Type: AWS::EFS::MountTarget Properties: FileSystemId: !Ref FileSystemResource SubnetId: !GetAtt VPCStack.Outputs.PrivateSubnet1 SecurityGroups: - !GetAtt EFSSecurityGroup.GroupId MountTargetResource2: Type: AWS::EFS::MountTarget Properties: FileSystemId: !Ref FileSystemResource SubnetId: !GetAtt VPCStack.Outputs.PrivateSubnet2 SecurityGroups: - !GetAtt EFSSecurityGroup.GroupId AccessPointResource: Type: AWS::EFS::AccessPoint Properties: FileSystemId: !Ref FileSystemResource PosixUser: Uid: '1000' Gid: '1000' RootDirectory: CreationInfo: OwnerGid: '1000' OwnerUid: '1000' Permissions: '755' Path: '/jenkins-home' JenkinsAgentSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: JenkinsAgentSecurityGroup GroupDescription: Security group for Jenkins agents VpcId: !GetAtt VPCStack.Outputs.VPC PrivateNamespace: Type: AWS::ServiceDiscovery::PrivateDnsNamespace Properties: Name: !Ref AWS::StackName Vpc: !GetAtt VPCStack.Outputs.VPC DiscoveryService: Type: AWS::ServiceDiscovery::Service Properties: DnsConfig: RoutingPolicy: MULTIVALUE DnsRecords: - TTL: 60 Type: A - TTL: 60 Type: SRV Name: jenkins NamespaceId: !Ref PrivateNamespace JenkinsPasswordSecret: Type: AWS::SecretsManager::Secret Properties: Name: JenkinsPasswordSecret GenerateSecretString: PasswordLength: 30 ExcludeCharacters: '"@/\' Outputs: LoadBalancerDNSName: Value: !GetAtt LoadBalancer.DNSName