Immutable Infrastructure Deployment€¦ · Immutable Infrastructure Deployment Nick Hibberd. data...

Post on 15-Oct-2020

12 views 1 download

Transcript of Immutable Infrastructure Deployment€¦ · Immutable Infrastructure Deployment Nick Hibberd. data...

Immutable Infrastructure Deployment

Nick Hibberd

data lake

Accidental chaos monkeys

Solutions

fib = 0 : scanl (+) 1 fib

1 import Control.Monad.State 2 fib n = flip evalState (0,1) $ do 3 forM [0..(n-1)] $ \_ -> do 4 (a,b) <- get 5 put (b,a+b) 6 (a,b) <- get 7 return a

0, 1, 1, 2, 3, 5, 8, …

1 commission image itype role tags userData subnet name azs sg = 2 resp <- send $ runInstances image 1 1 3 & rInstanceType .~ Just itype 4 & rBlockDeviceMappings .~ instanceDeviceMappings it 5 & rIAMInstanceProfile .~ 6 fmap (\r -> iamInstanceProfileSpecification & iapsName .~ Just r)) role 7 & rUserData .~ Just (userDataBase64 userData) 8 & rSubnetId .~ fmap subnetId subnet 9 & rPlacement .~ 10 ((\az -> placement 11 & pAvailabilityZone .~ Just (availabilityZone az)) <$> azs) 12 & rSecurityGroupIds .~ [securityGroupIdText sg] 13 i <- maybe liftInvariant (pure . InstanceId) . listToMaybe . 14 fmap (view insInstanceId) . view rInstances $ resp 15 16 send $ createTags 17 & cTags .~ (fmap ec2Tag $ nameTag name : filterReservedTags tags) 18 & cResources .~ [instanceId i] 19 20 pure i

Solving complex problems

with simple patterns

Concepts

v1

v1 v2

?

A

v1 v2

A

v1 v2

A A A

v1 v2

A A A

v1 v2

A A A

v1 v2

A A

v2

1 commission image itype role tags userData subnet name azs sg = 2 resp <- send $ runInstances image 1 1 3 & rInstanceType .~ Just itype 4 & rBlockDeviceMappings .~ instanceDeviceMappings it 5 & rIAMInstanceProfile .~ 6 fmap (\r -> iamInstanceProfileSpecification & iapsName .~ Just r)) role 7 & rUserData .~ Just (userDataBase64 userData) 8 & rSubnetId .~ fmap subnetId subnet 9 & rPlacement .~ 10 ((\az -> placement 11 & pAvailabilityZone .~ Just (availabilityZone az)) <$> azs) 12 & rSecurityGroupIds .~ [securityGroupIdText sg] 13 i <- maybe liftInvariant (pure . InstanceId) . listToMaybe . 14 fmap (view insInstanceId) . view rInstances $ resp 15 16 send $ createTags 17 & cTags .~ (fmap ec2Tag $ nameTag name : filterReservedTags tags) 18 & cResources .~ [instanceId i] 19 20 pure i

data Configuration = Configuration { capacity :: DesiredInstances , availabilityZones :: NonEmpty AvailabilityZone , instanceType :: InstanceType , market :: Market , security :: [SecurityGroup] , iam :: IamRole , software :: [Software] , users :: [Users] , flavour :: Flavour , elb :: [LoadBalancer] , context :: Context , service :: ServiceName } deriving (Eq, Show)

data Configuration = Configuration { capacity :: DesiredInstances , availabilityZones :: NonEmpty AvailabilityZone , instanceType :: InstanceType , market :: Market , security :: [SecurityGroup] , iam :: IamRole , software :: [Software] , users :: [Users] , flavour :: Flavour , elb :: [LoadBalancer] , context :: Context , service :: ServiceName } deriving (Eq, Show)

data Configuration = Configuration { capacity :: DesiredInstances , availabilityZones :: NonEmpty AvailabilityZone , instanceType :: InstanceType , market :: Market , security :: [SecurityGroup] , iam :: IamRole , software :: [Software] , users :: [Users] , flavour :: Flavour , elb :: [LoadBalancer] , context :: Context , service :: ServiceName } deriving (Eq, Show)

data Configuration = Configuration { capacity :: DesiredInstances , availabilityZones :: NonEmpty AvailabilityZone , instanceType :: InstanceType , market :: Market , security :: [SecurityGroup] , iam :: IamRole , software :: [Software] , users :: [Users] , flavour :: Flavour , elb :: [LoadBalancer] , context :: Context , service :: ServiceName } deriving (Eq, Show)

data Configuration = Configuration { capacity :: DesiredInstances , availabilityZones :: NonEmpty AvailabilityZone , instanceType :: InstanceType , market :: Market , security :: [SecurityGroup] , iam :: IamRole , software :: [Software] , users :: [Users] , flavour :: Flavour , elb :: [LoadBalancer] , context :: Context , service :: ServiceName } deriving (Eq, Show)

data Group = Group { groupName :: GroupName , groupConfiguration :: ConfigurationName , groupCreationTime :: UTCTime , groupCapacity :: DesiredInstances , groupAvailabilityZones :: NonEmpty AvailabilityZone , groupInstances :: [InstanceId] , groupInstanceHealth :: [InstanceHealth] , groupService :: ServiceName , groupLifeCycle :: LifeCycle , groupEnvironment :: Environment , groupELB :: [LoadBalancer] } deriving (Eq, Show)

data Group = Group { groupName :: GroupName , groupConfiguration :: ConfigurationName , groupCreationTime :: UTCTime , groupCapacity :: DesiredInstances , groupAvailabilityZones :: NonEmpty AvailabilityZone , groupInstances :: [InstanceId] , groupInstanceHealth :: [InstanceHealth] , groupService :: ServiceName , groupLifeCycle :: LifeCycle , groupEnvironment :: Environment , groupELB :: [LoadBalancer] } deriving (Eq, Show)

data Group = Group { groupName :: GroupName , groupConfiguration :: ConfigurationName , groupCreationTime :: UTCTime , groupCapacity :: DesiredInstances , groupAvailabilityZones :: NonEmpty AvailabilityZone , groupInstances :: [InstanceId] , groupInstanceHealth :: [InstanceHealth] , groupService :: ServiceName , groupLifeCycle :: LifeCycle , groupEnvironment :: Environment , groupELB :: [LoadBalancer] } deriving (Eq, Show)

data Group = Group { groupName :: GroupName , groupConfiguration :: ConfigurationName , groupCreationTime :: UTCTime , groupCapacity :: DesiredInstances , groupAvailabilityZones :: NonEmpty AvailabilityZone , groupInstances :: [InstanceId] , groupInstanceHealth :: [InstanceHealth] , groupService :: ServiceName , groupLifeCycle :: LifeCycle , groupEnvironment :: Environment , groupELB :: [LoadBalancer] } deriving (Eq, Show)

data Group = Group { groupName :: GroupName , groupConfiguration :: ConfigurationName , groupCreationTime :: UTCTime , groupCapacity :: DesiredInstances , groupAvailabilityZones :: NonEmpty AvailabilityZone , groupInstances :: [InstanceId] , groupInstanceHealth :: [InstanceHealth] , groupService :: ServiceName , groupLifeCycle :: LifeCycle , groupEnvironment :: Environment , groupELB :: [LoadBalancer] } deriving (Eq, Show)

Deployment as a function

Configuration -> [Group] -> ?

1 commission image itype role tags userData subnet name azs sg = 2 resp <- send $ runInstances image 1 1 3 & rInstanceType .~ Just itype 4 & rBlockDeviceMappings .~ instanceDeviceMappings it 5 & rIAMInstanceProfile .~ 6 fmap (\r -> iamInstanceProfileSpecification & iapsName .~ Just r)) role 7 & rUserData .~ Just (userDataBase64 userData) 8 & rSubnetId .~ fmap subnetId subnet 9 & rPlacement .~ 10 ((\az -> placement 11 & pAvailabilityZone .~ Just (availabilityZone az)) <$> azs) 12 & rSecurityGroupIds .~ [securityGroupIdText sg] 13 i <- maybe liftInvariant (pure . InstanceId) . listToMaybe . 14 fmap (view insInstanceId) . view rInstances $ resp 15 16 send $ createTags 17 & cTags .~ (fmap ec2Tag $ nameTag name : filterReservedTags tags) 18 & cResources .~ [instanceId i] 19 20 pure i

Configuration -> [Group] -> Goal

data Goal = CreateGroup Configuration | SetCapacity GroupName DesiredInstances | SetLifeCycle GroupName Resolved LifeCycle | NotYetHealthy GroupName [InstanceHealth] | DoNothing

data Goal = CreateGroup Configuration | SetCapacity GroupName DesiredInstances | SetLifeCycle GroupName Resolved LifeCycle | NotYetHealthy GroupName [InstanceHealth] | DoNothing

Deployment tool

AA A

A

AA A A A A A A

service x

A

Deployment tool

A

A

service x

A

Deployment tool

A

AA A A A A A A

A A A A A A A

AAdd 7

AA A A A A A A

A A

A

A A A A A A A

A A A A A A A

A A A A A A A

AA A

A

AA A A A A A A

A ASet total to 14

AA A A A A A A

AA A

A A A A A A A

Idempotent actions

data Goal = CreateGroup Configuration | SetCapacity GroupName DesiredInstances | SetLifeCycle GroupName Resolved LifeCycle | NotYetHealthy GroupName [InstanceHealth] | DoNothing

data Goal = CreateGroup Configuration | SetCapacity GroupName DesiredInstances | SetLifeCycle GroupName Resolved LifeCycle | NotYetHealthy GroupName [InstanceHealth] | DoNothing

data Goal = CreateGroup Configuration | SetCapacity GroupName DesiredInstances | SetLifeCycle GroupName Resolved LifeCycle | NotYetHealthy GroupName [InstanceHealth] | DoNothing

data Goal = CreateGroup Resolved | SetCapacity GroupName DesiredInstances | SetLifeCycle GroupName Resolved LifeCycle | NotYetHealthy GroupName [InstanceHealth] | DoNothing

data Goal = CreateGroup Resolved | SetCapacity GroupName DesiredInstances | SetLifeCycle GroupName Resolved LifeCycle | NotYetHealthy GroupName [InstanceHealth] | DoNothing

Rules

data Result a = TerminateFailure Error | TerminateSuccess Goal | Continue a

data Result a = TerminateFailure Error | TerminateSuccess Goal | Continue a

data Result a = TerminateFailure Error | TerminateSuccess Goal | Continue a

data Result a = TerminateFailure Error | TerminateSuccess Goal | Continue a

data Result a = TerminateFailure Error | TerminateSuccess Goal | Continue a

1 instance Monad Result where 2 (>>=) ma f = 3 case ma of 4 TerminateSuccess p -> 5 TerminateSuccess p 6 TerminateFailure be -> 7 TerminateFailure be 8 Continue a -> 9 f a 10 11 return = 12 Continue

1 instance Monad Result where 2 (>>=) ma f = 3 case ma of 4 TerminateSuccess p -> 5 TerminateSuccess p 6 TerminateFailure be -> 7 TerminateFailure be 8 Continue a -> 9 f a 10 11 return = 12 Continue

1 instance Monad Result where 2 (>>=) ma f = 3 case ma of 4 TerminateSuccess p -> 5 TerminateSuccess p 6 TerminateFailure be -> 7 TerminateFailure be 8 Continue a -> 9 f a 10 11 return = 12 Continue

1 instance Monad Result where 2 (>>=) ma f = 3 case ma of 4 TerminateSuccess p -> 5 TerminateSuccess p 6 TerminateFailure be -> 7 TerminateFailure be 8 Continue a -> 9 f a 10 11 return = 12 Continue

1 success :: Goal -> Result () 2 success p = 3 TerminateSuccess p

1 with :: Maybe a -> (a -> Result ()) -> Result () 2 with m f = 3 case m of 4 Just v -> 5 f v 6 Nothing -> 7 pure ()

Create a server

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 4 when (noMatchingVersions conf cs) . 5 success $ CreateGroup conf 6 7 ...

Aside

if software /= software' then if users /= users' then if permissions /= permissions' then if metadata /= metadata' then if location /= location' then if market /= market' then DONT DEPLOY else DEPLOY else DEPLOY else DEPLOY else DEPLOY else DEPLOY else DEPLOY

if software /= software' then if users /= users' then if permissions /= permissions' then if metadata /= metadata' then if location /= location' then if market /= market' then DONT DEPLOY else DEPLOY else DEPLOY else DEPLOY else DEPLOY else DEPLOY else DEPLOY

if users /= users' then if permissions /= permissions' then if metadata /= metadata' then if location /= location' then if market /= market' then DONT DEPLOY else DEPLOY else DEPLOY else DEPLOY else DEPLOY else DEPLOY

Hash

#

data HashPayload = HashPayload { hashSoftware :: [Software] , hashUsers :: [User] , hashSecurityGroups :: [SecurityGroup] , hashIam :: Iam , hashFlavour :: Flavour , hashInstanceType :: InstanceType , hashLoadBalancer :: [LoadBalancer] , hashRollover :: Maybe Rollover , hashMarket :: Market , hashClient :: Client , hashAvailabilityZone :: NonEmpty AvailabilityZone }

HashPayload -> Text

HashPayload -> Text

16848e7…137ec4

HashPayload [] [User "jimbob"] [SecurityGroup "ops"] (Iam "ops") (Flavour "base.image") T2_Nano [] None OnDemand (Client "ylj") (AvailabilityZone "ap-southeast-2c" :| [] )

1 noMatchingVersions :: Configuration -> [Group] -> Bool 2 noMatchingVersions conf groups = 3 null $ filter (\g -> hash conf == groupHash g) groups

Immutable infrastructure

2016-04-29

2016-04-29 -> 38651b2f9b047347a682eab7acc4a0459694fa22

2016-04-30 -> aa07113a5aab33559429223015bb90a33ce654f9

Desired

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 ... 4 5 with (desiredGroup conf cs) $ \r -> do 6 7 with (underCapacity conf r) $ \i -> 8 success . 9 SetCapacity (groupName r) $ increaseInstances i

v1 v2

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 ... 4 5 with (desiredGroup conf cs) $ \r -> do 6 7 with (underCapacity conf r) $ \i -> 8 success . 9 SetCapacity (groupName r) $ increaseInstances i

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 ... 4 5 with (desiredGroup conf cs) $ \r -> do 6 7 with (underCapacity conf r) $ \i -> 8 success . 9 SetCapacity (groupName r) $ increaseInstances i

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 ... 4 5 with (desiredGroup conf cs) $ \r -> do 6 ... 7 8 with (overCapacity conf r) $ \i -> 9 success . 10 SetCapacity (groupName r) $ decreaseInstances i

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 ... 4 5 with (desiredGroup conf cs) $ \r -> do 6 ... 7 8 with (overCapacity conf r) $ \i -> 9 success . 10 SetCapacity (groupName r) $ decreaseInstances i

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 ... 4 5 with (desiredGroup conf cs) $ \r -> do 6 ... 7 8 when (not $ whenHealthy r) . 9 success $ NotYetHealthy (groupName r) (groupInstanceHealth r)

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 ... 4 5 with (desiredGroup conf cs) $ \r -> do 6 7 with (underCapacity conf r) $ \i -> 8 success . 9 SetCapacity (groupName r) $ increaseInstances i 10 11 with (overCapacity conf r) $ \i -> 12 success . 13 SetCapacity (groupName r) $ decreaseInstances i 14 15 when (not $ whenHealthy r) . 16 success $ NotYetHealthy (groupName r) (groupInstanceHealth r)

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 ... 4 5 with (leastDesirableGroup conf cs)

v1 v2

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 ... 4 5 with (leastDesirableGroup conf cs) $ \g -> do 6 7 when (active g) . 8 success . SetLifeCycle (groupResultName g) conf $ Inactive

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 ... 4 5 with (leastDesirableGroup conf cs) $ \g -> do 6 7 ... 8 9 when (inactive g) . 10 success . SetCapacity (groupResultName g) $ DesiredInstances 0

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 4 when (noMatchingVersions conf cs) . 5 success $ CreateGroup conf 6 7 with (desiredGroup conf cs) $ \r -> do 8 9 with (underCapacity conf r) $ \i -> 10 success . 11 SetCapacity (groupName r) $ increaseInstances i 12 13 with (leastDesirableGroup conf cs) $ \g -> do 14 15 when (active g) . 16 success . SetLifeCycle (groupResultName g) conf $ Inactive 17 18 when (inactive g) . 19 success . SetCapacity (groupResultName g) $ DesiredInstances 0

Maintenance

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 4 when (noMatchingVersions conf cs) . 5 success $ CreateGroup conf 6 7 ...

v1 v2

v1 v2

v1 v2 v3

v1 v2 v3

v1 v2 v3 v4

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 4 when (noMatchingVersions conf cs) . 5 success $ CreateGroup conf 6 7 ...

1 calculate :: Configuration -> [Group] -> Result () 2 calculate conf cs = do 3 4 when (noMatchingVersions conf cs && length cs < 2) . 5 success $ CreateGroup conf 6 7 ...

v1 v2

v1 v2

v1 v2

v1 v2

v1 v2

v2

v2 v4

Loop School

The perfect program with haskell syntax

value <- read

let answer = calculate value

doit answer

The perfect Loop with haskell syntax

forever $ do

value <- read

let answer = calculate value

doit answer

value <- IO

let goals = calculate conf groups

doit goals

confs <- retrieveConfigurations

groups <- retrieveGroups

let goals = calculate conf groups

doit goals

Goal -> IO ()

1 runGoal :: Goal -> IO () 2 runGoal goal = 3 case goal of 4 CreateGroup b -> do 6 createGroup g 7

1 createGroup g = 2 continueIfExists . void . send $ A.createAutoScalingGroup 3 (renderName $ groupName g) 4 0 5 10 6 & A.casgAvailabilityZones .~ 7 Just (availabilityZone <$> groupAvailabilityZones g) 8 & A.casgTags .~ 9 (groupTags g) 10 & A.casgLaunchConfigurationName .~ 11 Just (configurationName $ groupConfName g) 12 & A.casgDesiredCapacity .~ 13 Just (desiredInstances . minimumDesiredInstances $ groupDesiredInstances g) 14 & A.casgLoadBalancerNames .~ 15 (loadBalancer <$> groupELB g)

1 runGoal :: Goal -> IO () 2 runGoal goal = 3 case goal of 4 CreateGroup b -> 5 ... 6 7 SetLifeCycle gn b l -> do 8 setLifeCycle gn l 9 when (l == Inactive) . lift $ 10 detachLoadBalancers gn

1 runGoal :: Goal -> IO () 2 runGoal goal = 3 case goal of 4 ... 5 6 SetCapacity gn di -> do 7 setCapacity gn di

1 runGoal :: Goal -> IO () 2 runGoal goal = 3 case goal of 4 ... 5 6 NotYetHealthy gn hs -> do 7 now <- getCurrentTime 8 case checkHealth now hs of 9 [] -> 10 pure () 11 unhealthy -> 12 interverene gn unhealthy

1 runGoal :: Goal -> IO () 2 runGoal goal = 3 case goal of 4 ... 5 6 DoNothing -> 7 pure ()

1 runGoal :: Goal -> IO () 2 runGoal goal = 3 case goal of 4 CreateGroup b -> do 5 createConfiguration c 6 createGroup g 7 8 SetLifeCycle gn b l -> do 9 setLifeCycle gn l 10 when (l == Inactive) . lift $ 11 detachLoadBalancers gn 12 13 SetCapacity gn di -> do 14 setCapacity gn di 15 16 NotYetHealthy gn hs -> do 17 now <- getCurrentTime 18 case checkHealth now hs of 19 [] -> 20 pure () 21 unhealthy -> 22 interverene gn unhealthy 23 24 DoNothing -> 25 pure ()

1 work :: IO () 2 work = do 3 4 confs <- retrieveConfigurations 5 6 groups <- retrieveGroups 7 8 let goals = calculateAll confs groups 9 10 mapM runGoal goal

1 workForever :: IO () 2 workForever = 3 forever $ work

Immutable Infrastructure Deployment