There are a number of ways to do this. Discussed in comments is vertex parenting; another way is to use a shrinkwrap constraint. Different techniques are often going to give different results-- there's more than one way to snap to the face.
I'm comparing a shrinkwrap constraint to vertex parenting here. The shrinkwrap constraint is shown; it depends on the object axes of the "eye" because it is set to align the object with the normal of the target. We can see that when the "head" deforms, the constrained objects remain basically where they were, and just snap to the nearest point of the mesh object. Note that we're not fully defining our rotation here, and because of that, we might see changes in the target mesh leading to apparent rotation about the Z axis of the eye; it depends on the starting rotation and the nature of the target.
On the right, we see that vertex parenting doesn't behave the same way. I've parented these eyes not just with vertex parenting, but with vertex (triangle), parenting them to three vertices (the three closest, chosen symmetrically for each pupil.) These actually move with the head, following its rotation and movement-- we see that when we stretch the head up, the eyes move up, unlike with the shrinkwrap constraint.
Either of these are computed live, adapting to each frame, to each change you make to the head. But if you intend to use in other applications than Blender, you will probably need to bake the animation after it's finished, as these techniques are specific to Blender and don't get exported.