immutable infrastructure deployment€¦ · immutable infrastructure deployment nick hibberd. data...
TRANSCRIPT
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