<p>I have two pods namely payroll and mysql labelled as <code>name=payroll</code> and <code>name=mysql</code>. There's another pod named internal with label <code>name=internal</code>. I am trying to allow egress traffic from internal to other two pods while allowing all ingress traffic. My <code>NetworkPoliy</code> looks like this:</p>

<pre><code>apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: internal-policy
spec:
  podSelector:
    matchLabels:
      name: internal
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - {}       
  egress:
  - to:
    - podSelector:
        matchExpressions:
          - {key: name, operator: In, values: [payroll, mysql]}
    ports:
    - protocol: TCP
      port: 8080
    - protocol: TCP
      port: 3306
</code></pre>

<p>This does not match the two pods payroll and mysql. What am I doing wrong?</p>

<p>The following works:</p>

<pre><code>apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: internal-policy
spec:
  podSelector:
    matchLabels:
      name: internal
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - {}       
  egress:
  - to:
    - podSelector:
        matchLabels:
          name: payroll
    - podSelector:
        matchLabels:
          name: mysql
    ports:
    - protocol: TCP
      port: 8080
    - protocol: TCP
      port: 3306
</code></pre>

<p>What is the best way to write a <code>NetWorkPolicy</code> and why is the first one incorrect?</p>

<p>I also am wondering why the <code>to</code> field is an array while the <code>podSelector</code> is also an array inside it? I mean they are the same right? Multiple <code>podSelector</code> or multiple <code>to</code> fields. Using one of them works.</p>

<blockquote>
  <p>This does not match the two pods payroll and mysql. What am I doing wrong?</p>
</blockquote>

<ul>
<li>I've reproduce your scenarios with pod-to-service and pod-to-pod environments, in both cases <strong>both yamls worked</strong> well. That said after fixing the indentation on line 19 where both <code>podSelector</code> should be in the same level, as follows:</li>
</ul>

<pre><code>  - to:
    - podSelector:
        matchLabels:
          name: payroll
    - podSelector:
        matchLabels:
          name: mysql
</code></pre>

<hr>

<blockquote>
  <p>What is the best way to write a <code>NetWorkPolicy</code>?</p>
</blockquote>

<ul>
<li>The best one depends on each scenario, it's a good practice to create one networkpolicy for each rule. I'd say the first yaml is the best one if you intend to expose ports <code>8080</code> and <code>3306</code> on BOTH pods, otherwise it would be better to create two rules, to avoid leaving unnecessary open ports.</li>
</ul>

<hr>

<blockquote>
  <p>I also am wondering why the <code>to</code> field is an array while the <code>podSelector</code> is also an array inside it? I mean they are the same right? Multiple <code>podSelector</code> or multiple <code>to</code> fields. Using one of them works.</p>
</blockquote>

<ul>
<li><p>From <a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#networkpolicyspec-v1-networking-k8s-io" rel="nofollow noreferrer">NetworkPolicySpec v1 networking API Ref:</a></p>

<blockquote>
  <p><code>egress</code> <a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#networkpolicyegressrule-v1-networking-k8s-io" rel="nofollow noreferrer">NetworkPolicyEgressRule array</a>:
  List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod, <strong>OR</strong> if the traffic <strong>matches at least one</strong> egress rule <strong>across all of the NetworkPolicy objects</strong> whose podSelector matches the pod.</p>
</blockquote></li>
<li><p>Also keep in mind that this list also includes the <a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#networkpolicyport-v1-networking-k8s-io" rel="nofollow noreferrer">Ports</a> Array as well.</p></li>
</ul>

<hr>

<blockquote>
  <p>Why is the first one incorrect?</p>
</blockquote>

<ul>
<li>Both rules are basically the same, only written in different formats. I'd say you should check if there is any other rule in effect for the same labels.</li>
<li>I'd suggest you to create a test cluster and try applying the step-by-step example I'll leave below.</li>
</ul>

<hr>

<p><strong>Reproduction:</strong></p>

<ul>
<li>This example is very similar to your case. I'm using <code>nginx</code> images for it's easy testing and changed ports to <code>80</code> on <code>NetworkPolicy</code>. I'm calling your first yaml <code>internal-original.yaml</code> and the second you posted <code>second-internal.yaml</code>:</li>
</ul>

<pre><code>$ cat internal-original.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: internal-original
spec:
  podSelector:
    matchLabels:
      name: internal
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - {}
  egress:
  - to:
    - podSelector:
        matchExpressions:
          - {key: name, operator: In, values: [payroll, mysql]}
    ports:
    - protocol: TCP
      port: 80

$ cat second-internal.yaml 
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: internal-policy
spec:
  podSelector:
    matchLabels:
      name: internal
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - {}       
  egress:
  - to:
    - podSelector:
        matchLabels:
          name: payroll
    - podSelector:
        matchLabels:
          name: mysql
    ports:
    - protocol: TCP
      port: 80
</code></pre>

<ul>
<li>Now we create the pods with the labels and expose the services:</li>
</ul>

<pre><code>$ kubectl run mysql --generator=run-pod/v1 --labels="name=mysql" --image=nginx 
pod/mysql created
$ kubectl run internal --generator=run-pod/v1 --labels="name=internal" --image=nginx
pod/internal created
$ kubectl run payroll --generator=run-pod/v1 --labels="name=payroll" --image=nginx
pod/payroll created
$ kubectl run other --generator=run-pod/v1 --labels="name=other" --image=nginx
pod/other created

$ kubectl expose pod mysql --port=80
service/mysql exposed
$ kubectl expose pod payroll --port=80
service/payroll exposed
$ kubectl expose pod other --port=80
service/other exposed
</code></pre>

<ul>
<li>Now, before applying the <code>networkpolicy</code>, I'll log into the <code>internal</code> pod to download <code>wget</code>, because after that outside access will be blocked:</li>
</ul>

<pre><code>$ kubectl exec internal -it -- /bin/bash
root@internal:/# apt update
root@internal:/# apt install wget -y
root@internal:/# exit
</code></pre>

<ul>
<li>Since your rule is blocking access to DNS, I'll list the IPs and test with them:</li>
</ul>

<pre><code>$ kubectl get pods -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP
internal   1/1     Running   0          62s   10.244.0.192
mysql      1/1     Running   0          74s   10.244.0.141
other      1/1     Running   0          36s   10.244.0.216
payroll    1/1     Running   0          48s   10.244.0.17 

$ kubectl get services
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
mysql        ClusterIP   10.101.209.87   &lt;none&gt;        80/TCP    23s
other        ClusterIP   10.103.39.7     &lt;none&gt;        80/TCP    9s
payroll      ClusterIP   10.109.102.5    &lt;none&gt;        80/TCP    14s
</code></pre>

<ul>
<li>Now let's test the access with the first yaml:</li>
</ul>

<pre><code>$ kubectl get networkpolicy
No resources found in default namespace.

$ kubectl apply -f internal-original.yaml 
networkpolicy.networking.k8s.io/internal-original created

$ kubectl exec internal -it -- /bin/bash
root@internal:/# wget --spider --timeout=1 http://10.101.209.87
Spider mode enabled. Check if remote file exists.
--2020-06-08 18:17:55--  http://10.101.209.87/
Connecting to 10.101.209.87:80... connected.
HTTP request sent, awaiting response... 200 OK

root@internal:/# wget --spider --timeout=1 http://10.109.102.5
Spider mode enabled. Check if remote file exists.
--2020-06-08 18:18:04--  http://10.109.102.5/
Connecting to 10.109.102.5:80... connected.
HTTP request sent, awaiting response... 200 OK

root@internal:/# wget --spider --timeout=1 http://10.103.39.7
Spider mode enabled. Check if remote file exists.
--2020-06-08 18:18:08--  http://10.103.39.7/
Connecting to 10.103.39.7:80... failed: Connection timed out.
</code></pre>

<ul>
<li>Now let's test the access with the second yaml:</li>
</ul>

<pre><code>$ kubectl get networkpolicy
NAME                POD-SELECTOR    AGE
internal-original   name=internal   96s

$ kubectl delete networkpolicy internal-original
networkpolicy.networking.k8s.io "internal-original" deleted

$ kubectl apply -f second-internal.yaml 
networkpolicy.networking.k8s.io/internal-policy created

$ kubectl exec internal -it -- /bin/bash
root@internal:/# wget --spider --timeout=1 http://10.101.209.87
Spider mode enabled. Check if remote file exists.
--2020-06-08 17:18:24--  http://10.101.209.87/
Connecting to 10.101.209.87:80... connected.
HTTP request sent, awaiting response... 200 OK

root@internal:/# wget --spider --timeout=1 http://10.109.102.5
Spider mode enabled. Check if remote file exists.
--2020-06-08 17:18:30--  http://10.109.102.5/
Connecting to 10.109.102.5:80... connected.
HTTP request sent, awaiting response... 200 OK

root@internal:/# wget --spider --timeout=1 http://10.103.39.7
Spider mode enabled. Check if remote file exists.
--2020-06-08 17:18:35--  http://10.103.39.7/
Connecting to 10.103.39.7:80... failed: Connection timed out.
</code></pre>

<ul>
<li>As you can see, the connection to the services with the labels were ok and the connection to the pod that has other label has failed.</li>
</ul>

<p><strong>Note:</strong> If you wish to allow pods to resolve DNS, you can follow this guide: <a href="https://github.com/ahmetb/kubernetes-network-policy-recipes/blob/master/11-deny-egress-traffic-from-an-application.md#allowing-dns-traffic" rel="nofollow noreferrer">Allow DNS Egress Traffic</a></p>

<p>If you have any questions, let me know in the comments.</p>
